ManageDrawer.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. <template>
  2. <el-drawer
  3. v-model="isDrawer"
  4. size="480"
  5. :title="props.type === 'add' ? '添加监控调阅记录' : '编辑监控调阅记录'"
  6. @close="handleCloseDrawer"
  7. >
  8. <el-form ref="formRef" :model="formParams" :rules="rules" label-placement="left" :label-width="95">
  9. <el-form-item label="工号" prop="staffNo" style="margin-bottom: 8px">
  10. <el-input placeholder="请输入工号" v-model="formParams.staffNo" v-if="staffNoHtmlType === 'INPUT'" />
  11. <el-tree-select
  12. v-model="formParams.staffNo"
  13. check-strictly
  14. placeholder="请输入工号进行搜索"
  15. class="protocal-select"
  16. filterable
  17. remote
  18. clearable
  19. :loading="loading"
  20. :data="staffNoOptions"
  21. :render-after-expand="false"
  22. :default-expand-all="true"
  23. :remote-method="debouncedRemoteMethod"
  24. @clear="handleClearStaffNo"
  25. @change="handleChangeStaffNo"
  26. v-else
  27. />
  28. <el-text class="text-mode" type="primary" @click="handleChangeStaff">{{
  29. `切换为工号${staffNoHtmlType === 'INPUT' ? '选择' : '输入'}方式`
  30. }}</el-text>
  31. </el-form-item>
  32. <el-form-item label="姓名" prop="userName">
  33. <el-input
  34. :placeholder="staffNoHtmlType === 'INPUT' ? '请输入姓名' : '请选择工号,此项自动填充'"
  35. v-model="formParams.userName"
  36. :disabled="staffNoHtmlType === 'SELECT'"
  37. />
  38. </el-form-item>
  39. <el-form-item label="所属部门" prop="deptName">
  40. <el-tree-select
  41. v-model="formParams.deptName"
  42. :data="departmentArr"
  43. :render-after-expand="false"
  44. :default-expand-all="true"
  45. check-strictly
  46. :placeholder="staffNoHtmlType === 'INPUT' ? '请选择部门' : '请选择工号,此项自动填充'"
  47. class="protocal-select"
  48. :filter-node-method="filterDept"
  49. filterable
  50. clearable
  51. :disabled="staffNoHtmlType === 'SELECT'"
  52. />
  53. </el-form-item>
  54. <el-form-item label="调阅位置" prop="accessLocation">
  55. <el-input
  56. placeholder="请输入调阅位置"
  57. v-model="formParams.accessLocation"
  58. maxlength="50"
  59. show-word-limit
  60. clearable
  61. />
  62. </el-form-item>
  63. <el-form-item label="调阅时段" prop="accessTimeRange">
  64. <el-date-picker
  65. v-model="formParams.accessTimeRange"
  66. type="datetimerange"
  67. range-separator="至"
  68. start-placeholder="开始时间"
  69. end-placeholder="结束时间"
  70. format="YYYY-MM-DD HH:mm:ss"
  71. value-format="YYYY-MM-DD HH:mm:ss"
  72. style="width: 100%"
  73. @change="handleChangeAccessTimeRange"
  74. />
  75. </el-form-item>
  76. <el-form-item label="是否拷贝" prop="isCopy">
  77. <el-radio-group v-model="formParams.isCopy">
  78. <el-radio :value="0">否</el-radio>
  79. <el-radio :value="1">是</el-radio>
  80. </el-radio-group>
  81. </el-form-item>
  82. <el-form-item label="调阅状态" prop="accessStatus">
  83. <el-radio-group v-model="formParams.accessStatus">
  84. <el-radio :value="0">待调阅</el-radio>
  85. <el-radio :value="1">已调阅</el-radio>
  86. </el-radio-group>
  87. </el-form-item>
  88. <el-form-item label="审批单上传" prop="approvalFormUrl" :rules="approvalFormRules">
  89. <UploadImages
  90. ref="uploadImagesRef"
  91. :maxCount="1"
  92. :image-list="approvalImageList"
  93. @upload-success="handleApprovalUploadChange"
  94. />
  95. </el-form-item>
  96. <el-form-item label="记录人" prop="createdByName">
  97. <el-input placeholder="记录人" v-model="formParams.createdByName" disabled />
  98. </el-form-item>
  99. </el-form>
  100. <template #footer>
  101. <el-space>
  102. <el-button @click="handleCloseDrawer">取消</el-button>
  103. <el-button type="primary" @click="handleSubmitForm(formRef)">提交</el-button>
  104. </el-space>
  105. </template>
  106. </el-drawer>
  107. </template>
  108. <script lang="ts" setup>
  109. import { ref, reactive, watch, computed, onMounted } from 'vue';
  110. import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
  111. import { debounce } from 'lodash-es';
  112. import { getAllDepartments } from '@/api/auth/dept';
  113. import { queryOrganizationUserTree } from '@/api/system/user';
  114. import { OrganizationUserTree } from '@/views/system/user/types';
  115. import { calculateTreeData } from '@/utils';
  116. import UploadImages from '@/views/disaster/disaster-control/src/components/UploadImages.vue';
  117. import { ImageItem } from '@/types/disaster-control';
  118. import { UPLOAD_BIZ_TYPE, uploadFileApi } from '@/api/minio';
  119. import {
  120. findUserByWorkNo,
  121. transformTreeData,
  122. TransformedTreeNode,
  123. findOrgCodeByWorkNo,
  124. } from '@/utils/findUserByWorkNo';
  125. import {
  126. SurveillanceInfoStruct,
  127. addSurveillanceInfo,
  128. updateSurveillanceInfo,
  129. } from '@/api/security-confidentiality-surveillance';
  130. import { useUserInfoHook } from '@/hooks/useUserInfoHook';
  131. const props = defineProps<{
  132. type: string; // add or edit
  133. initialData: SurveillanceInfoStruct;
  134. }>();
  135. const emits = defineEmits<{
  136. (e: 'close'): void;
  137. }>();
  138. const { id, realname, staffNo } = useUserInfoHook();
  139. const formRef = ref<FormInstance>();
  140. const formParams = ref<SurveillanceInfoStruct & { accessTimeRange: [string, string] }>({
  141. id: 0,
  142. staffNo: '',
  143. userName: '',
  144. deptId: 0,
  145. deptName: '',
  146. accessLocation: '',
  147. accessStartTime: '',
  148. accessEndTime: '',
  149. accessTimeRange: ['', ''],
  150. isCopy: 0,
  151. accessStatus: 0,
  152. approvalFormUrl: '',
  153. createdById: id,
  154. createdByName: realname,
  155. createdByStaffNo: staffNo,
  156. createdAt: '',
  157. updatedAt: '',
  158. isDeleted: 0,
  159. });
  160. const rules = reactive<FormRules<SurveillanceInfoStruct & { accessTimeRange: [string, string] }>>({
  161. staffNo: { required: true, message: '工号不能为空', trigger: 'blur' },
  162. userName: { required: true, message: '姓名不能为空', trigger: 'blur' },
  163. deptName: { required: true, message: '所属部门不能为空', trigger: 'blur' },
  164. accessLocation: { required: true, message: '调阅位置不能为空', trigger: 'blur' },
  165. accessTimeRange: { required: true, message: '调阅时段不能为空', trigger: 'change' },
  166. isCopy: { required: true, message: '请选择是否拷贝', trigger: 'change' },
  167. accessStatus: { required: true, message: '请选择调阅状态', trigger: 'change' },
  168. });
  169. // 审批单上传验证规则
  170. const approvalFormRules = [
  171. {
  172. validator: (_: unknown, value: string, callback: (err?: Error) => void) => {
  173. if (!value || value.trim() === '') {
  174. return callback(new Error('请上传审批单'));
  175. }
  176. callback();
  177. },
  178. trigger: ['blur', 'change'],
  179. },
  180. ];
  181. const isDrawer = ref(true);
  182. const loading = ref(false);
  183. const staffNoOptions = ref<TransformedTreeNode[]>([]); // 工号选择列表
  184. const OrganizationSourceData = ref<OrganizationUserTree[]>([]); // 组织结构树原始数据
  185. const departmentArr = ref<{ value: string | number; label: string }[]>([]);
  186. const sourceDepartArr = ref<{ value: string | number; label: string }[]>([]);
  187. // 工号输入/选择模式切换
  188. type STAFFNO_HTML_TYPE = 'INPUT' | 'SELECT';
  189. const staffNoHtmlType = ref<STAFFNO_HTML_TYPE>('SELECT');
  190. // 审批单上传相关
  191. const uploadImagesRef = ref<InstanceType<typeof UploadImages>>();
  192. const approvalImages = ref<ImageItem[]>([]);
  193. const approvalImageList = computed(() => {
  194. if (!formParams.value.approvalFormUrl) return [];
  195. // 如果是单个URL字符串,转换为数组格式
  196. try {
  197. const parsed = JSON.parse(formParams.value.approvalFormUrl);
  198. return Array.isArray(parsed) ? parsed : [parsed];
  199. } catch {
  200. // 如果不是JSON格式,直接作为单个URL处理
  201. return [{ url: formParams.value.approvalFormUrl }];
  202. }
  203. });
  204. // 通过工号查询组织结构树
  205. const remoteMethod = (query: string) => {
  206. if (query) {
  207. loading.value = true;
  208. queryOrganizationUserTree(Number(query)).then((res) => {
  209. if (res) {
  210. loading.value = false;
  211. staffNoOptions.value = transformTreeData(res, true);
  212. OrganizationSourceData.value = res;
  213. departmentArr.value = transformTreeData(OrganizationSourceData.value, false);
  214. }
  215. });
  216. } else {
  217. staffNoOptions.value = [];
  218. }
  219. };
  220. // 防抖
  221. const debouncedRemoteMethod = debounce(remoteMethod, 1000);
  222. const handleClearStaffNo = () => {
  223. formParams.value.staffNo = '';
  224. formParams.value.userName = '';
  225. formParams.value.deptId = undefined;
  226. formParams.value.deptName = '';
  227. };
  228. // 切换工号输入/选择模式
  229. const handleChangeStaff = () => {
  230. staffNoHtmlType.value = staffNoHtmlType.value === 'INPUT' ? 'SELECT' : 'INPUT';
  231. if (staffNoHtmlType.value === 'INPUT') {
  232. // 切换到输入模式时,清空工号、姓名和部门,并恢复部门列表
  233. formParams.value.staffNo = '';
  234. formParams.value.userName = '';
  235. formParams.value.deptId = undefined;
  236. formParams.value.deptName = '';
  237. departmentArr.value = sourceDepartArr.value;
  238. } else {
  239. // 切换到选择模式时,清空工号、姓名和部门
  240. formParams.value.staffNo = '';
  241. formParams.value.userName = '';
  242. formParams.value.deptId = undefined;
  243. formParams.value.deptName = '';
  244. }
  245. };
  246. // 递归查找树结构中的节点
  247. const findNodeInTree = (tree: any[], value: string | number): any => {
  248. for (const node of tree) {
  249. // 检查当前节点
  250. if (node.value === value) {
  251. return node;
  252. }
  253. // 检查子节点(如果有)
  254. if (node.children && node.children.length > 0) {
  255. const foundNode = findNodeInTree(node.children, value);
  256. if (foundNode) {
  257. return foundNode;
  258. }
  259. }
  260. }
  261. return null;
  262. };
  263. const handleChangeStaffNo = (value) => {
  264. const findUser = findUserByWorkNo(OrganizationSourceData.value, value);
  265. const deptId = Number(findOrgCodeByWorkNo(OrganizationSourceData.value, value));
  266. const dept = findNodeInTree(departmentArr.value, deptId);
  267. if (findUser) {
  268. formParams.value.staffNo = findUser.idtUserWorkNo;
  269. formParams.value.userName = findUser.appAccountAccountName;
  270. formParams.value.deptId = deptId;
  271. formParams.value.deptName = dept ? dept.label : '';
  272. }
  273. };
  274. const handleChangeAccessTimeRange = () => {
  275. console.log('accessTimeRange', formParams.value.accessTimeRange);
  276. if (formParams.value.accessTimeRange && formParams.value.accessTimeRange.length === 2) {
  277. formParams.value.accessStartTime = formParams.value.accessTimeRange[0];
  278. formParams.value.accessEndTime = formParams.value.accessTimeRange[1];
  279. }
  280. };
  281. // 格式化审批单图片
  282. const formatApprovalImage = async (file: File) => {
  283. if (!file) return '';
  284. const fileName = file.name;
  285. const res = await uploadFileApi({ bizType: UPLOAD_BIZ_TYPE.ATTACHMENT, fileName, file });
  286. return res.url;
  287. };
  288. const handleApprovalUploadChange = async () => {
  289. approvalImages.value = uploadImagesRef.value!.getUploadedImages();
  290. if (approvalImages.value && approvalImages.value.length > 0) {
  291. const image = approvalImages.value[0]; // 只取第一张图片
  292. if (!image.file && image.url) {
  293. // 如果已有URL,直接使用
  294. formParams.value.approvalFormUrl = image.url;
  295. } else if (image.file) {
  296. // 如果有新文件,上传后使用新URL
  297. const url = await formatApprovalImage(image.file);
  298. formParams.value.approvalFormUrl = url;
  299. }
  300. } else {
  301. // 如果没有图片,清空URL
  302. formParams.value.approvalFormUrl = '';
  303. }
  304. // 手动触发审批单字段的校验
  305. if (formRef.value) {
  306. formRef.value.validateField('approvalFormUrl');
  307. }
  308. };
  309. // 部门过滤函数
  310. const filterDept = (val: string, data: any) => {
  311. return data.label.includes(val);
  312. };
  313. // 初始化部门列表
  314. const initDepartmentList = () => {
  315. const departmentList = ref<{ value: string | number; label: string }[]>([]);
  316. getAllDepartments().then((res) => {
  317. departmentList.value = calculateTreeData(res, { level: 10, valueKey: 'id', labelKey: 'deptName' }, 1);
  318. sourceDepartArr.value = departmentList.value;
  319. departmentArr.value = departmentList.value;
  320. });
  321. };
  322. const handleCloseDrawer = () => {
  323. emits('close');
  324. };
  325. const handleSubmitForm = async (formEl: FormInstance | undefined) => {
  326. if (!formEl) return;
  327. // 处理调阅时段
  328. if (formParams.value.accessTimeRange && formParams.value.accessTimeRange.length === 2) {
  329. formParams.value.accessStartTime = formParams.value.accessTimeRange[0];
  330. formParams.value.accessEndTime = formParams.value.accessTimeRange[1];
  331. }
  332. await formEl.validate((valid) => {
  333. if (!valid) return;
  334. if (props.type === 'add') {
  335. addSurveillanceInfo(formParams.value).then(() => {
  336. ElMessage.success('添加成功');
  337. handleCloseDrawer();
  338. });
  339. } else {
  340. updateSurveillanceInfo(formParams.value).then(() => {
  341. ElMessage.success('编辑成功');
  342. handleCloseDrawer();
  343. });
  344. }
  345. });
  346. };
  347. watch(
  348. () => props.initialData,
  349. (newData) => {
  350. if (newData) {
  351. formParams.value = { ...newData, accessTimeRange: ['', ''] };
  352. // 处理调阅时段
  353. if (newData.accessStartTime && newData.accessEndTime) {
  354. formParams.value.accessTimeRange = [newData.accessStartTime, newData.accessEndTime];
  355. }
  356. // 处理审批单图片 - 直接使用URL字符串
  357. // approvalFormUrl 已经是字符串格式,不需要额外处理
  358. // 如果有工号,尝试查询用户信息
  359. if (newData.staffNo) {
  360. remoteMethod(newData.staffNo);
  361. }
  362. // 编辑模式下,记录人字段保持原有数据,不覆盖
  363. }
  364. },
  365. { immediate: true, deep: true },
  366. );
  367. onMounted(() => {
  368. initDepartmentList();
  369. // 只有在添加模式下才初始化记录人为当前登录用户
  370. if (props.type === 'add') {
  371. formParams.value.createdByName = realname;
  372. }
  373. });
  374. </script>
  375. <style lang="scss" scoped>
  376. .protocal-select:deep(.el-select-dropdown__wrap) {
  377. max-height: 600px;
  378. }
  379. .text-mode {
  380. cursor: pointer;
  381. font-size: 12px;
  382. margin-left: 4px;
  383. &:hover {
  384. text-decoration: underline;
  385. }
  386. }
  387. </style>