|
|
@@ -180,7 +180,11 @@
|
|
|
<el-button v-if="!isViewMode" type="primary" @click="onSave">提交</el-button>
|
|
|
</template>
|
|
|
<!-- <SelectDeviceDialog v-model:visible="showSelectDeviceDialog" @confirm="onDeviceSelected" /> -->
|
|
|
- <SelectCatgory v-model:visible="showSelectDeviceDialog" @confirm="onDeviceSelected" />
|
|
|
+ <SelectCatgory
|
|
|
+ v-model:visible="showSelectDeviceDialog"
|
|
|
+ :disabled-ids="selectedDeviceCategoryIds"
|
|
|
+ @confirm="onDeviceSelected"
|
|
|
+ />
|
|
|
</el-dialog>
|
|
|
<el-dialog v-model="dialogVisible">
|
|
|
<img w-full :src="dialogImageUrl" alt="Preview Image" />
|
|
|
@@ -530,6 +534,100 @@ const form = ref({
|
|
|
classroom: ''
|
|
|
});
|
|
|
const showSelectDeviceDialog = ref(false);
|
|
|
+/** 详情回填后的应用用户借用人 id;编辑态下仅在与该 id 一致时不从用户列表覆盖 borrower_dept(列表侧组织字段可能不准) */
|
|
|
+const lastHydratedAppUserBorrowerId = ref<number | null>(null);
|
|
|
+
|
|
|
+function normalizeAppUserBorrowerId(raw: any): number | null {
|
|
|
+ if (raw == null || raw === '') return null;
|
|
|
+ if (typeof raw === 'object' && raw.id != null && raw.id !== '') {
|
|
|
+ const n = Number(raw.id);
|
|
|
+ return Number.isFinite(n) ? n : null;
|
|
|
+ }
|
|
|
+ const n = Number(raw);
|
|
|
+ return Number.isFinite(n) ? n : null;
|
|
|
+}
|
|
|
+
|
|
|
+/** 与用户列表项、详情 borrower_info 等结构对齐,从组织字段推导展示用借用部门 */
|
|
|
+function computeBorrowerDeptFromUserLike(selectedUser: any): string {
|
|
|
+ if (!selectedUser || typeof selectedUser !== 'object') return '';
|
|
|
+ let deptName = '';
|
|
|
+ const orgDetail: any = selectedUser.organization_detail || {};
|
|
|
+ const parentChain: any[] = Array.isArray(orgDetail.parent_chain) ? orgDetail.parent_chain : [];
|
|
|
+ const isValid = (val: any) => {
|
|
|
+ const s = String(val).trim();
|
|
|
+ return val !== null && val !== undefined && s !== '' && s.toLowerCase() !== 'nan' && !Number.isNaN(val);
|
|
|
+ };
|
|
|
+ if (selectedUser.user_type === 0) {
|
|
|
+ const chainNames: string[] = parentChain
|
|
|
+ .filter((n: any) => Number(n?.type) !== 1)
|
|
|
+ .map((n: any) => n?.name)
|
|
|
+ .filter((n: any) => isValid(n));
|
|
|
+ const college = chainNames[1] || '';
|
|
|
+ const major = chainNames[2] || selectedUser.sub_organization || '';
|
|
|
+ const clazz = orgDetail.name || selectedUser.class_or_group || '';
|
|
|
+ const parts = [college, major, clazz].filter((p) => isValid(p));
|
|
|
+ deptName = parts.join('/');
|
|
|
+ if (!deptName) {
|
|
|
+ const full = selectedUser.full_organization_name || '';
|
|
|
+ deptName = full.replace(/^学校\s*\/\s*/, '');
|
|
|
+ deptName = deptName
|
|
|
+ .split('/')
|
|
|
+ .map((s: string) => s.trim())
|
|
|
+ .filter((s: string) => isValid(s))
|
|
|
+ .join('/');
|
|
|
+ }
|
|
|
+ deptName = deptName
|
|
|
+ .split('/')
|
|
|
+ .map((s: string) => s.trim())
|
|
|
+ .filter((s: string) => isValid(s))
|
|
|
+ .join('/');
|
|
|
+ } else if (selectedUser.user_type === 1 || selectedUser.user_type === 3) {
|
|
|
+ deptName = orgDetail?.name || selectedUser.organization || selectedUser.full_organization_name || '';
|
|
|
+ } else {
|
|
|
+ deptName = selectedUser.full_organization_name || '';
|
|
|
+ }
|
|
|
+ return isValid(deptName) ? deptName : '';
|
|
|
+}
|
|
|
+
|
|
|
+/** 详情接口有时不落 borrower_dept 字段,仅从 borrower_info 带组织;不能用当前登录人部门回填否则会误显「Admin团队」等 */
|
|
|
+function applyBorrowerDeptFromDetailPayload(data: any) {
|
|
|
+ const f: any = form.value;
|
|
|
+ const root = data?.borrower_dept;
|
|
|
+ if (root != null && String(root).trim() !== '') {
|
|
|
+ f.borrower_dept = String(root).trim();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const bi = data?.borrower_info;
|
|
|
+ if (!bi || typeof bi !== 'object') return;
|
|
|
+ const nestedDept = bi.dept_info?.dept_name;
|
|
|
+ if (nestedDept != null && String(nestedDept).trim() !== '') {
|
|
|
+ f.borrower_dept = String(nestedDept).trim();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const fromProfile = computeBorrowerDeptFromUserLike(bi);
|
|
|
+ if (fromProfile) f.borrower_dept = fromProfile;
|
|
|
+}
|
|
|
+
|
|
|
+/** 将详情中的借用人统一为 el-select 可用的数字 id,并同步 lastHydratedAppUserBorrowerId */
|
|
|
+function normalizeBorrowerFieldsAfterDetailAssign(data: any) {
|
|
|
+ const f: any = form.value;
|
|
|
+ let bid = normalizeAppUserBorrowerId(f.app_user_borrower);
|
|
|
+ if (bid == null && data?.borrower_info?.id != null) {
|
|
|
+ f.app_user_borrower = Number(data.borrower_info.id);
|
|
|
+ bid = normalizeAppUserBorrowerId(f.app_user_borrower);
|
|
|
+ } else if (bid != null) {
|
|
|
+ f.app_user_borrower = bid;
|
|
|
+ }
|
|
|
+ lastHydratedAppUserBorrowerId.value = normalizeAppUserBorrowerId(f.app_user_borrower);
|
|
|
+ applyBorrowerDeptFromDetailPayload(data);
|
|
|
+}
|
|
|
+
|
|
|
+/** 已在借用列表中的设备分类 id,选择弹窗中不可重复勾选 */
|
|
|
+const selectedDeviceCategoryIds = computed(() =>
|
|
|
+ (form.value.items || [])
|
|
|
+ .map((it: any) => it.device_category ?? it.device)
|
|
|
+ .filter((id: any) => id !== null && id !== undefined && id !== '')
|
|
|
+);
|
|
|
const isViewMode = computed(() => props.modelValue?.mode === 'view');
|
|
|
|
|
|
|
|
|
@@ -562,9 +660,9 @@ const rules = {
|
|
|
expected_end_time: [
|
|
|
{ required: true, message: '请输选择归还时间', trigger: 'change' },
|
|
|
],
|
|
|
- team_members:[
|
|
|
+ /* team_members:[
|
|
|
{ required: true, message: '请输入团队其他人员名单', trigger: 'change' },
|
|
|
- ],
|
|
|
+ ], */
|
|
|
team_type: [
|
|
|
{ required: true, message: '请选择个人或者团队', trigger: 'change' },
|
|
|
],
|
|
|
@@ -584,7 +682,7 @@ onMounted(() => {
|
|
|
form.value.external_borrower_name = userInfo.username || '';
|
|
|
// app_user_borrower 初始化不选择,由用户手动选择
|
|
|
form.value.external_borrower_phone = userInfo.mobile || '';
|
|
|
- form.value.borrower_dept = userInfo.dept_info?.dept_name || '';
|
|
|
+ // 不写入 borrower_dept:操作员(如管理员)的 dept_name 常为「Admin团队」,详情接口若未带 borrower_dept 会残留误显
|
|
|
}
|
|
|
} catch (e) {console.error(e)}
|
|
|
fetchWeeklySchedule();
|
|
|
@@ -594,63 +692,31 @@ onMounted(() => {
|
|
|
|
|
|
// 监听借用人选择变化
|
|
|
watch(() => form.value.app_user_borrower, (newValue) => {
|
|
|
- if (newValue && allUserList.value.length > 0) {
|
|
|
- const selectedUser = allUserList.value.find(user => Number(user.id) === newValue);
|
|
|
- if (selectedUser) {
|
|
|
- form.value.user_code = selectedUser.user_code || '';
|
|
|
- form.value.mobile = selectedUser.mobile || '';
|
|
|
- // 根据用户类型动态设置借用部门:
|
|
|
- // 学生(user_type===0):学院/专业/班级;教师或领导(user_type===1或3):学院
|
|
|
- let deptName: string = '';
|
|
|
- const orgDetail: any = (selectedUser as any).organization_detail || {};
|
|
|
- const parentChain: any[] = Array.isArray(orgDetail.parent_chain) ? orgDetail.parent_chain : [];
|
|
|
- const isValid = (val: any) => {
|
|
|
- const s = String(val).trim();
|
|
|
- return val !== null && val !== undefined && s !== '' && s.toLowerCase() !== 'nan' && !Number.isNaN(val);
|
|
|
- };
|
|
|
- if ((selectedUser as any).user_type === 0) {
|
|
|
- // 学生:优先按 parent_chain 顺序拼接“学院/专业/班级”,过滤掉“学校”层级
|
|
|
- const chainNames: string[] = parentChain
|
|
|
- .filter((n: any) => Number(n?.type) !== 1) // 1=学校,过滤
|
|
|
- .map((n: any) => n?.name)
|
|
|
- .filter((n: any) => isValid(n));
|
|
|
- // 取前3级:学院(0)/专业(1)/班级(2)
|
|
|
- const college = chainNames[1] || '';
|
|
|
- const major = chainNames[2] || (selectedUser as any).sub_organization || '';
|
|
|
- const clazz = orgDetail.name || (selectedUser as any).class_or_group || '';
|
|
|
- const parts = [college, major, clazz].filter((p) => isValid(p));
|
|
|
- deptName = parts.join('/');
|
|
|
- if (!deptName) {
|
|
|
- // 兜底:若 parent_chain 不可用,则使用已有汇总字段但尽量去掉“学校/”前缀
|
|
|
- const full = (selectedUser as any).full_organization_name || '';
|
|
|
- deptName = full.replace(/^学校\s*\/\s*/,'');
|
|
|
- // 兜底后继续清洗 'nan' 与空白段
|
|
|
- deptName = deptName
|
|
|
- .split('/')
|
|
|
- .map((s: string) => s.trim())
|
|
|
- .filter((s: string) => isValid(s))
|
|
|
- .join('/');
|
|
|
- }
|
|
|
- // 最终统一清洗,避免出现重复斜杠或 'nan'
|
|
|
- deptName = deptName
|
|
|
- .split('/')
|
|
|
- .map((s: string) => s.trim())
|
|
|
- .filter((s: string) => isValid(s))
|
|
|
- .join('/');
|
|
|
- } else if ((selectedUser as any).user_type === 1 || (selectedUser as any).user_type === 3) {
|
|
|
- // 教师或学院领导:显示学院名称
|
|
|
- deptName = orgDetail?.name || (selectedUser as any).organization || (selectedUser as any).full_organization_name || '';
|
|
|
- } else {
|
|
|
- // 其他类型兜底
|
|
|
- deptName = (selectedUser as any).full_organization_name || '';
|
|
|
- }
|
|
|
- form.value.borrower_dept = isValid(deptName)?deptName:'';
|
|
|
-
|
|
|
- // 如果是新创建的用户,提示用户填写完整信息
|
|
|
- if (selectedUser.isNew) {
|
|
|
- ElMessage.info('请为新用户填写学号/工号和联系电话');
|
|
|
- }
|
|
|
+ // 查看态:借用部门、学号等一律以详情接口为准,避免用户列表组织信息不完整覆盖已保存值(如误显「admin团队」等)
|
|
|
+ if (isView.value) return;
|
|
|
+ const nid = normalizeAppUserBorrowerId(newValue);
|
|
|
+ if (nid == null || !allUserList.value.length) return;
|
|
|
+ const selectedUser = allUserList.value.find(user => Number(user.id) === nid);
|
|
|
+ if (!selectedUser) return;
|
|
|
+
|
|
|
+ form.value.user_code = selectedUser.user_code || '';
|
|
|
+ form.value.mobile = selectedUser.mobile || '';
|
|
|
+
|
|
|
+ // 编辑态且仍为详情刚回填的同一借用人:若已从详情得到非空部门则不再用列表覆盖(列表组织字段可能不准)
|
|
|
+ const hasDeptFromDetail = String(form.value.borrower_dept || '').trim() !== '';
|
|
|
+ const skipDeptFromUserList =
|
|
|
+ isEdit.value && nid === lastHydratedAppUserBorrowerId.value && hasDeptFromDetail;
|
|
|
+ if (skipDeptFromUserList) {
|
|
|
+ if (selectedUser.isNew) {
|
|
|
+ ElMessage.info('请为新用户填写学号/工号和联系电话');
|
|
|
}
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ form.value.borrower_dept = computeBorrowerDeptFromUserLike(selectedUser);
|
|
|
+
|
|
|
+ if (selectedUser.isNew) {
|
|
|
+ ElMessage.info('请为新用户填写学号/工号和联系电话');
|
|
|
}
|
|
|
});
|
|
|
|
|
|
@@ -694,42 +760,79 @@ function onUserCodeBlur() {
|
|
|
queryUserByCode(userCode);
|
|
|
}
|
|
|
|
|
|
+function hasDraftSnapshot(snap: Record<string, any> | undefined | null): boolean {
|
|
|
+ return !!(snap && typeof snap === 'object' && Object.keys(snap).length > 0);
|
|
|
+}
|
|
|
+
|
|
|
watch(() => props.visible, (v) => {
|
|
|
- visible.value = v;
|
|
|
-// console.log("快照visible.value::",visible.value)
|
|
|
- if (v) {
|
|
|
- if (props.lastFormSnapshot) {
|
|
|
- console.log("快照lastFormSnapshot.value::",props.lastFormSnapshot )
|
|
|
- Object.assign((form.value as any), (props.lastFormSnapshot as any) || {});
|
|
|
- } else {
|
|
|
- resetForm(); // 如果没有快照,初始化表单
|
|
|
- }
|
|
|
- }
|
|
|
+ visible.value = v;
|
|
|
+ if (!v) return;
|
|
|
+ lastHydratedAppUserBorrowerId.value = null;
|
|
|
+ const mode = props.modelValue?.mode;
|
|
|
+ // 编辑/查看:表单以详情接口为准,不合并 lastFormSnapshot,避免上次新增草稿或编辑数据混入
|
|
|
+ if (mode === 'edit' || mode === 'view') {
|
|
|
+ resetForm();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 仅新增:有非空快照时恢复用户未提交前填写的内容(空对象 {} 不再误判为「有快照」)
|
|
|
+ if (hasDraftSnapshot(props.lastFormSnapshot as Record<string, any>)) {
|
|
|
+ Object.assign(form.value as any, props.lastFormSnapshot as any);
|
|
|
+ } else {
|
|
|
+ resetForm();
|
|
|
+ }
|
|
|
}, { immediate: true });
|
|
|
|
|
|
|
|
|
|
|
|
-watch(() => props.modelValue, async (val) => {
|
|
|
- if (val && val.id && isView.value) {
|
|
|
- try {
|
|
|
+watch(() => props.modelValue, async (val, oldVal) => {
|
|
|
+ // 新增且无 id:不拉详情;若刚从带 id 的查看/编辑上下文切过来,清掉误注入的详情(父组件曾晚一帧更新 modelValue 时的兜底)
|
|
|
+ if (val && typeof val === 'object' && val.mode === 'add' && !val.id) {
|
|
|
+ lastHydratedAppUserBorrowerId.value = null;
|
|
|
+ const ov: any = oldVal;
|
|
|
+ const wasDetailContext = !!(
|
|
|
+ ov &&
|
|
|
+ typeof ov === 'object' &&
|
|
|
+ (ov.id || ov.mode === 'view' || ov.mode === 'edit')
|
|
|
+ );
|
|
|
+ if (wasDetailContext) {
|
|
|
+ if (hasDraftSnapshot(props.lastFormSnapshot as Record<string, any>)) {
|
|
|
+ resetForm();
|
|
|
+ Object.assign(form.value as any, props.lastFormSnapshot as any);
|
|
|
+ } else {
|
|
|
+ resetForm();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (val && val.id && isView.value) {
|
|
|
+ const expectId = val.id;
|
|
|
+ const expectMode = val.mode;
|
|
|
+ try {
|
|
|
const res = await api.GetApplicationDetail(val.id);
|
|
|
+ const mv: any = props.modelValue;
|
|
|
+ if (!mv || mv.id !== expectId || mv.mode !== expectMode) return;
|
|
|
if (res.code === 2000 && res.data) {
|
|
|
- const data = res.data;
|
|
|
- Object.assign(form.value, data);
|
|
|
+ Object.assign(form.value, res.data);
|
|
|
+ normalizeBorrowerFieldsAfterDetailAssign(res.data);
|
|
|
}
|
|
|
- } catch (e) {
|
|
|
+ } catch (e) {
|
|
|
ElMessage.error('获取详细信息失败');
|
|
|
- }
|
|
|
- }else if(val&& isEdit.value){
|
|
|
+ }
|
|
|
+ } else if (val && isEdit.value && val.id) {
|
|
|
+ const expectId = val.id;
|
|
|
+ const expectMode = val.mode;
|
|
|
try {
|
|
|
const res = await api.GetApplicationDetail(val.id);
|
|
|
+ const mv: any = props.modelValue;
|
|
|
+ if (!mv || mv.id !== expectId || mv.mode !== expectMode) return;
|
|
|
if (res.code === 2000 && res.data) {
|
|
|
- const data = res.data;
|
|
|
- Object.assign(form.value, data);
|
|
|
+ Object.assign(form.value, res.data);
|
|
|
+ normalizeBorrowerFieldsAfterDetailAssign(res.data);
|
|
|
}
|
|
|
- } catch (e) {
|
|
|
+ } catch (e) {
|
|
|
ElMessage.error('获取详细信息失败');
|
|
|
- }
|
|
|
+ }
|
|
|
}
|
|
|
}, { immediate: true });
|
|
|
|
|
|
@@ -783,7 +886,10 @@ function onSave() {
|
|
|
|
|
|
emit('submit', submitData);
|
|
|
emit('update:visible', false);
|
|
|
- emit('update:lastFormSnapshot', cloneDeep(form.value));
|
|
|
+ // 仅保存「新增」草稿;编辑提交不回写快照,避免下次新增带出编辑单数据
|
|
|
+ if (!isEdit.value) {
|
|
|
+ emit('update:lastFormSnapshot', cloneDeep(form.value));
|
|
|
+ }
|
|
|
}
|
|
|
});
|
|
|
}
|