|
|
|
|
@ -0,0 +1,351 @@
|
|
|
|
|
<template>
|
|
|
|
|
<el-dialog :model-value="visible" class="hospital-edit-dialog" top="6vh" width="780px" destroy-on-close
|
|
|
|
|
:close-on-click-modal="false" @update:model-value="(v) => !v && close()">
|
|
|
|
|
<div class="body-wrap">
|
|
|
|
|
<div class="body-header">
|
|
|
|
|
<span class="body-title">
|
|
|
|
|
{{ isEdit ? $t("modal.editHospital") : $t("modal.addHospital") }}
|
|
|
|
|
</span>
|
|
|
|
|
<img src="@/assets/image/close-btn.svg" alt="" class="close-btn" @click="close">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="hospital-edit" v-loading="loading">
|
|
|
|
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="130px">
|
|
|
|
|
<el-row :gutter="20">
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-form-item :label="$t('label.hospitalName')" prop="name">
|
|
|
|
|
<el-input v-model="form.name" :placeholder="$t('placeholder.hospital')" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-form-item :label="$t('label.category')" prop="customerCategory">
|
|
|
|
|
<el-select v-model="form.customerCategory" :placeholder="$t('label.category')" style="width: 200px">
|
|
|
|
|
<el-option label="A类客户(活跃)" value="A" />
|
|
|
|
|
<el-option label="B类客户(不活跃)" value="B" />
|
|
|
|
|
<el-option label="C类客户(待挽回)" value="C" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-form-item :label="$t('label.contactDept')">
|
|
|
|
|
<el-input v-model="form.contactDept" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-form-item :label="$t('label.contactPerson')">
|
|
|
|
|
<el-input v-model="form.contactPerson" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
<el-row :gutter="20">
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-form-item :label="$t('label.contactPosition')">
|
|
|
|
|
<el-input v-model="form.contactPosition" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-form-item :label="$t('label.contactPhone')">
|
|
|
|
|
<el-input v-model="form.contactPhone" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-form-item :label="$t('label.signDate')" prop="signDate">
|
|
|
|
|
<el-date-picker v-model="form.signDate" type="date" value-format="YYYY-MM-DD"
|
|
|
|
|
:placeholder="$t('common.pleaseSelect')" style="width: 200px" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-form-item :label="$t('label.acceptDate')" prop="acceptDate">
|
|
|
|
|
<el-date-picker v-model="form.acceptDate" type="date" value-format="YYYY-MM-DD"
|
|
|
|
|
:placeholder="$t('common.pleaseSelect')" style="width: 200px" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<el-row :gutter="20">
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-form-item :label="$t('label.years')" prop="contractYears">
|
|
|
|
|
<el-input-number v-model="form.contractYears" :min="1" :precision="0" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-form-item :label="$t('label.maintenanceEndDate')" prop="maintenanceEnd">
|
|
|
|
|
<el-date-picker v-model="form.maintenanceEnd" type="date" value-format="YYYY-MM-DD"
|
|
|
|
|
:placeholder="$t('common.pleaseSelect')" style="width: 200px" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
<el-col :span="6">
|
|
|
|
|
<el-form-item :label="$t('label.manager')">
|
|
|
|
|
<el-select v-model="form.managerId" :placeholder="$t('label.manager')" filterable clearable
|
|
|
|
|
style="width: 240px">
|
|
|
|
|
<el-option v-for="item in managerOptions" :key="item.id" :label="`${item.name}`" :value="item.id" />
|
|
|
|
|
</el-select>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-col>
|
|
|
|
|
</el-row>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<el-form-item :label="$t('label.acceptanceReport')" required>
|
|
|
|
|
<el-upload v-model:file-list="reportFileList" :auto-upload="false" :limit="1"
|
|
|
|
|
:on-exceed="handleReportExceed" accept=".pdf">
|
|
|
|
|
<el-button type="primary" plain>{{ $t("btn.upload") }}</el-button>
|
|
|
|
|
<template #tip>
|
|
|
|
|
<div class="el-upload__tip">
|
|
|
|
|
{{ $t("label.acceptanceReportTip") }}
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-upload>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item :label="$t('common.remark')">
|
|
|
|
|
<el-input v-model="form.remark" type="textarea" :rows="3" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
</el-form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div class="body-footer">
|
|
|
|
|
<el-button @click="close">{{ $t("common.cancel") }}</el-button>
|
|
|
|
|
<el-button type="primary" :loading="submitting" @click="submit">
|
|
|
|
|
{{ isEdit ? $t("common.save") : $t("common.add") }}
|
|
|
|
|
</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</el-dialog>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
import { computed, reactive, ref, watch } from "vue";
|
|
|
|
|
import { ElMessage } from "element-plus";
|
|
|
|
|
import { useI18n } from "vue-i18n";
|
|
|
|
|
import {
|
|
|
|
|
getAdminHospitalDetail,
|
|
|
|
|
createAdminHospital,
|
|
|
|
|
updateAdminHospital,
|
|
|
|
|
getAdminAccounts,
|
|
|
|
|
} from "@/service/modular/admin";
|
|
|
|
|
|
|
|
|
|
const { t: $t } = useI18n();
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
visible: { type: Boolean, default: false },
|
|
|
|
|
// 父组件传入的医院 id,为空时为新增
|
|
|
|
|
hospitalId: { type: [String, Number], default: "" },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits(["update:visible", "close", "success"]);
|
|
|
|
|
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
const submitting = ref(false);
|
|
|
|
|
const formRef = ref();
|
|
|
|
|
const managerLoaded = ref(false);
|
|
|
|
|
|
|
|
|
|
const isEdit = computed(() => !!props.hospitalId);
|
|
|
|
|
|
|
|
|
|
// 详情接口返回的原始验收报告路径,为空表示无报告或已被用户替换
|
|
|
|
|
const form = reactive({
|
|
|
|
|
name: "",
|
|
|
|
|
customerCategory: "",
|
|
|
|
|
contactDept: "",
|
|
|
|
|
contactPerson: "",
|
|
|
|
|
contactPosition: "",
|
|
|
|
|
contactPhone: "",
|
|
|
|
|
signDate: "",
|
|
|
|
|
acceptDate: "",
|
|
|
|
|
contractYears: 1,
|
|
|
|
|
maintenanceEnd: "",
|
|
|
|
|
managerId: undefined,
|
|
|
|
|
remark: "",
|
|
|
|
|
attachmentPath: "",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const rules = {
|
|
|
|
|
name: [
|
|
|
|
|
{
|
|
|
|
|
required: true,
|
|
|
|
|
message: () => $t("placeholder.hospital"),
|
|
|
|
|
trigger: "blur",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
customerCategory: [
|
|
|
|
|
{ required: true, message: () => $t("label.category"), trigger: "change" },
|
|
|
|
|
],
|
|
|
|
|
signDate: [
|
|
|
|
|
{ required: true, message: () => $t("label.signDate"), trigger: "change" },
|
|
|
|
|
],
|
|
|
|
|
acceptDate: [
|
|
|
|
|
{
|
|
|
|
|
required: true,
|
|
|
|
|
message: () => $t("label.acceptDate"),
|
|
|
|
|
trigger: "change",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
contractYears: [
|
|
|
|
|
{ required: true, message: () => $t("label.years"), trigger: "blur" },
|
|
|
|
|
],
|
|
|
|
|
maintenanceEnd: [
|
|
|
|
|
{
|
|
|
|
|
required: true,
|
|
|
|
|
message: () => $t("label.maintenanceEndDate"),
|
|
|
|
|
trigger: "change",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const managerOptions = ref([]);
|
|
|
|
|
|
|
|
|
|
const reportFileList = ref([]);
|
|
|
|
|
|
|
|
|
|
// 超出 limit 时仅保留最后一个
|
|
|
|
|
// 触发即代表用户上传了新文件替换原报告,清空 attachmentPath
|
|
|
|
|
const handleReportExceed = (files) => {
|
|
|
|
|
reportFileList.value = files.slice(-1);
|
|
|
|
|
form.attachmentPath = "";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 从带路径分隔符的地址中截取最后的文件名(兼容 / 与 \)
|
|
|
|
|
const getAttachmentFileName = (path) => {
|
|
|
|
|
if (!path) return "";
|
|
|
|
|
return String(path).replace(/\\/g, "/").split("/").pop() || "";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 重置表单到初始状态,弹窗每次打开时使用
|
|
|
|
|
const resetForm = () => {
|
|
|
|
|
Object.assign(form, {
|
|
|
|
|
name: "",
|
|
|
|
|
customerCategory: "",
|
|
|
|
|
contactDept: "",
|
|
|
|
|
contactPerson: "",
|
|
|
|
|
contactPosition: "",
|
|
|
|
|
contactPhone: "",
|
|
|
|
|
signDate: "",
|
|
|
|
|
acceptDate: "",
|
|
|
|
|
contractYears: 1,
|
|
|
|
|
maintenanceEnd: "",
|
|
|
|
|
managerId: undefined,
|
|
|
|
|
remark: "",
|
|
|
|
|
attachmentPath: "",
|
|
|
|
|
});
|
|
|
|
|
reportFileList.value = [];
|
|
|
|
|
formRef.value?.clearValidate();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const loadManagers = async () => {
|
|
|
|
|
if (managerLoaded.value) return;
|
|
|
|
|
try {
|
|
|
|
|
const res = await getAdminAccounts({ type: "Admin" });
|
|
|
|
|
managerOptions.value = res?.list || [];
|
|
|
|
|
managerLoaded.value = true;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// request 层已统一提示错误
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const loadDetail = async (id) => {
|
|
|
|
|
if (!id) return;
|
|
|
|
|
loading.value = true;
|
|
|
|
|
try {
|
|
|
|
|
const data = await getAdminHospitalDetail(id);
|
|
|
|
|
Object.assign(form, data);
|
|
|
|
|
// 若详情接口返回了验收报告路径,截取文件名以"已上传"状态回显到 file-list,
|
|
|
|
|
// 位置与正常上传后一致;不下载,仅展示以便用户决定是否替换
|
|
|
|
|
const fileName = getAttachmentFileName(data?.attachmentPath);
|
|
|
|
|
if (fileName) {
|
|
|
|
|
reportFileList.value = [
|
|
|
|
|
{
|
|
|
|
|
uid: `attachment-${id}`,
|
|
|
|
|
name: fileName,
|
|
|
|
|
status: "success",
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const buildHospitalFormData = () => {
|
|
|
|
|
const fd = new FormData();
|
|
|
|
|
Object.entries(form).forEach(([key, value]) => {
|
|
|
|
|
if (value === undefined || value === null || value === "") return;
|
|
|
|
|
fd.append(key, value);
|
|
|
|
|
});
|
|
|
|
|
// 兼容 el-upload 在新增/编辑两种场景下 file-list 项的结构差异:
|
|
|
|
|
// 新增时:item 为 UploadFile 对象,包含 raw 指向 File
|
|
|
|
|
// 编辑时:item 本身即为 File 对象(el-upload 直接以原始 File 替换了回显项)
|
|
|
|
|
// 回显项无 raw 且 size 未定义,命中后自然不会走到 files 提交
|
|
|
|
|
const item = (reportFileList.value || [])[0];
|
|
|
|
|
if (item) {
|
|
|
|
|
const file = item.raw || (typeof item.size === "number" ? item : null);
|
|
|
|
|
if (file) fd.append("files", file);
|
|
|
|
|
}
|
|
|
|
|
return fd;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const submit = async () => {
|
|
|
|
|
await formRef.value.validate(async (valid) => {
|
|
|
|
|
if (!valid) return;
|
|
|
|
|
// 仅在"新增"或"编辑但原报告已被清空/移除"且 file-list 为空时,才提示需上传
|
|
|
|
|
const keepOriginal = isEdit.value && !!form.attachmentPath;
|
|
|
|
|
if (!reportFileList.value?.length && !keepOriginal) {
|
|
|
|
|
ElMessage.warning($t("label.acceptanceReport"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
submitting.value = true;
|
|
|
|
|
try {
|
|
|
|
|
const fd = buildHospitalFormData();
|
|
|
|
|
if (isEdit.value) {
|
|
|
|
|
await updateAdminHospital(props.hospitalId, fd);
|
|
|
|
|
ElMessage.success($t("msg.hospitalEditSuccess"));
|
|
|
|
|
} else {
|
|
|
|
|
await createAdminHospital(fd);
|
|
|
|
|
ElMessage.success($t("msg.hospitalAddSuccess"));
|
|
|
|
|
}
|
|
|
|
|
emit("success");
|
|
|
|
|
emit("update:visible", false);
|
|
|
|
|
} finally {
|
|
|
|
|
submitting.value = false;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const close = () => emit("update:visible", false);
|
|
|
|
|
|
|
|
|
|
// 弹窗打开或 id 变化时重新加载数据,与 detail.vue 行为一致
|
|
|
|
|
watch(
|
|
|
|
|
() => [props.visible, props.hospitalId],
|
|
|
|
|
([vis, id]) => {
|
|
|
|
|
if (vis) {
|
|
|
|
|
resetForm();
|
|
|
|
|
loadManagers();
|
|
|
|
|
if (id) loadDetail(id);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{ immediate: true }
|
|
|
|
|
);
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
.hospital-edit {
|
|
|
|
|
max-height: 70vh;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
padding-right: 4px;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
|
.hospital-edit-dialog {
|
|
|
|
|
min-width: 1168px;
|
|
|
|
|
max-width: 90vw;
|
|
|
|
|
|
|
|
|
|
.body-header {
|
|
|
|
|
margin-bottom: 18px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|