| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495 |
- <template>
- <div class="basic-info">
- <div class="caseTitle">案件信息</div>
- <!-- 展示模式:按来源路由分支展示 -->
- <div class="show-view-content" v-if="props.editOrShow === 'show'">
- <!-- criminal 展示 -->
- <div v-if="props.fromRoute === 'criminal'" class="camera-from show-view">
- <!-- <div class="form-title">案件信息</div> -->
- <div class="info-row"><span class="label">案件名称:</span><span class="value">{{ bindFire?.caseTitle || '-' }}</span></div>
- <div class="info-row"><span class="label">详细地址:</span><span class="value">{{ bindFire?.mapUrl || '-' }}</span></div>
- </div>
- <!-- fire 展示(原有) -->
- <div v-else class="camera-from show-view">
- <div class="all-content no-scrollbar">
- <!-- <div class="form-title">案件信息</div> -->
- <div class="info-row"><span class="label">项目编号:</span><span class="value">{{ bindFire.projectSn || '-' }}</span></div>
- <div class="info-row"><span class="label">起火对象:</span><span class="value">{{ bindFire.projectName || '-' }}</span></div>
- <div class="info-row"><span class="label">详细地址:</span><span class="value">{{ bindFire.mapUrl || '-' }}</span></div>
- <div class="info-row"><span class="label">起火地址:</span><span class="value">{{ bindFire.projectAddress || '-' }}</span></div>
- <div class="info-row"><span class="label">起火场所:</span><span class="value">{{ bindFire.projectSite || '-' }}</span></div>
- <div class="info-row"><span class="label">承办单位:</span><span class="value">{{ bindFire.organizerDeptName || '-' }}</span></div>
- <div class="info-row"><span class="label">承办人员:</span><span class="value">{{ bindFire.organizerUsers || '-' }}</span></div>
- <div class="info-row"><span class="label">事故日期:</span><span class="value">{{ bindFire.accidentDate || '-' }}</span></div>
- <div class="info-row"><span class="label">火灾原因:</span><span class="value">{{ bindFire.fireReason || '-' }}</span></div>
- </div>
- </div>
- </div>
- <!-- 编辑模式:按来源路由分支展示 -->
- <template v-else>
- <!-- criminal 编辑(复原 edit.vue) -->
- <el-form v-if="props.fromRoute === 'criminal'" ref="form" label-width="96px" class="camera-from">
- <div class="all-content">
- <!-- <div class="form-title">案件信息</div> -->
- <el-form-item label="案件名称">
- <el-input v-model="bindFire.caseTitle" maxlength="50" placeholder="请输入案件名称" />
- </el-form-item>
- <el-form-item label="详细地址" class="mandatory">
- <el-input v-model="bindFire.mapUrl" placeholder="输入名称搜索" clearable disabled>
- <template #append>
- <el-button :icon="Search" @click="searchAMapAddress" />
- </template>
- </el-input>
- </el-form-item>
- </div>
- </el-form>
- <!-- fire 编辑(原有) -->
- <el-form v-else ref="form" label-width="100px" class="camera-from">
- <div class="all-content">
- <!-- <div class="form-title">案件信息</div> -->
- <el-form-item label="项目编号" class="mandatory">
- <el-input
- v-model="bindFire.projectSn"
- maxlength="18"
- placeholder="请输入项目编号"
- />
- </el-form-item>
- <el-form-item label="起火对象" class="mandatory">
- <el-input
- v-model="bindFire.projectName"
- maxlength="50"
- placeholder="请输入起火对象"
- />
- </el-form-item>
- <el-form-item label="详细地址">
- <el-input
- v-model="bindFire.mapUrl"
- placeholder="输入名称搜索"
- clearable
- readonly
- disabled
- class="mandatory"
- >
- <template #append>
- <el-button :icon="Search" @click="searchAMapAddress" />
- </template>
- </el-input>
- </el-form-item>
- <el-form-item label="起火地址" class="mandatory">
- <el-input
- v-model="bindFire.projectAddress"
- maxlength="50"
- placeholder="请输入起火地址"
- />
- </el-form-item>
- <el-form-item label="起火场所" class="mandatory">
- <el-cascader
- style="width: 100%"
- v-model="projectSite"
- placeholder="起火场所"
- :options="place"
- :props="{ expandTrigger: 'hover' }"
- />
- </el-form-item>
- <el-form-item label="承办单位" class="mandatory">
- <companySelect v-model="bindFire.deptId" hideAll :notUpdate="true" disabled />
- </el-form-item>
- <el-form-item label="承办人员" class="mandatory" placeholder="请输入承办人员">
- <el-input v-model="bindFire.organizerUsers" maxlength="50" />
- </el-form-item>
- <el-form-item label="事故日期" class="mandatory" placeholder="请选择事故日期">
- <el-date-picker
- type="date"
- v-model="accidentDate"
- style="width: 100%"
- :disabled-date="(date) => date.getTime() > new Date().getTime()"
- />
- </el-form-item>
- <el-form-item label="火灾原因" class="mandatory">
- <el-cascader
- style="width: 100%"
- v-model="fireReason"
- placeholder="火灾原因:"
- :options="reason"
- :props="{ expandTrigger: 'hover' }"
- />
- </el-form-item>
- </div>
- </el-form>
- </template>
- </div>
- <!-- 地图弹窗仅 fire 使用 -->
- <!-- 地图弹窗改为统一使用,无论路由来源 -->
- <creatMap v-model="showMapDialog" :caseId="caseId" @confirm="handleMapConfirm" />
- </template>
- <script setup lang="ts">
- import companySelect from "@/components/company-select/index.vue";
- import { ref, watch, toRef, onMounted, onUnmounted, onBeforeUnmount } from "vue";
- import { Fire, setFire, addFire } from "@/app/fire/store/fire";
- import { reason, place } from "@/app/fire/constant/fire";
- import { ElMessage } from "element-plus";
- import { dateFormat, debounce } from "@/util";
- import { genCascaderValue, getCode } from "@/helper/cascader";
- import { QuiskExpose } from "@/helper/mount";
- import { user } from "@/store/user";
- import { Search } from "@element-plus/icons-vue";
- // 旧的选择地图逻辑已统一为 creatMap 弹窗,移除 selectMapImage
- import { Example, setExample as setCriminalExample, addExample as addCriminalExample } from "@/app/criminal/store/example";
- import { getCaseInfoOffline as getCaseInfo } from "@/store/editCsae";
- import creatMap from "./creatMap.vue";
- const props = defineProps<{ fire?: Fire, caseId?: number, editOrShow?: string, fromRoute?: string }>();
- const caseId = toRef(props, 'caseId');
- let bindFire = ref<Fire>( props.fire ? { ...props.fire } : ({ deptId: user.value.info.deptId} as Fire));
- const accidentDate = ref(
- bindFire.value.accidentDate ? new Date(bindFire.value.accidentDate) : new Date()
- );
- watch(() => props.fire, (newVal, oldVal) => {
- bindFire.value = newVal ? { ...newVal } : ({ deptId: user.value.info.deptId} as Fire)
- accidentDate.value = bindFire.value.accidentDate ? new Date(bindFire.value.accidentDate) : new Date()
- })
- const dispatchRecordUpdate = () => {
- try {
- const title = props.fromRoute === 'criminal' ? (bindFire.value as any).caseTitle : (bindFire.value as any).projectName;
- const mapUrl = (bindFire.value as any).mapUrl;
- const latAndLong = (bindFire.value as any).latAndLong;
- window.dispatchEvent(
- new CustomEvent('fireDetails:updateTitle', {
- detail: {
- title,
- mapUrl,
- latAndLong,
- fire: props.fromRoute === 'fire' ? { ...(bindFire.value as any) } : undefined,
- criminal: props.fromRoute === 'criminal' ? { ...(bindFire.value as any) } : undefined,
- },
- })
- );
- } catch (e) {}
- };
- const showMapDialog = ref(false)
- const fireReason = genCascaderValue(bindFire, "fireReason");
- const projectSite = genCascaderValue(bindFire, "projectSite");
- const firstReload = ref(0)
- // ========== 自动保存(有变化就保存,无需按钮) ==========
- // 仅在数据满足基本必填项时才触发保存;使用防抖避免频繁请求
- const isValidForAutoSave = () => {
- const v = bindFire.value;
- // 自动保存不弹错误,只在必填项齐备时保存
- return (
- !!v.latAndLong && !!v.projectAddress && !!v.projectSn && !!v.projectName &&
- !!v.projectSite && !!v.deptId && !!v.organizerUsers && !!fireReason.value && !!accidentDate.value
- );
- };
- // 用于避免重复保存同一状态
- let lastSavedSnapshot = "";
- const getSnapshot = () => {
- const v = { ...bindFire.value } as any;
- v.accidentDate = dateFormat(accidentDate.value, "yyyy-MM-dd");
- v.projectSiteCode = getCode(place, bindFire.value.projectSite);
- return JSON.stringify({
- projectSn: v.projectSn,
- projectName: v.projectName,
- mapUrl: v.mapUrl,
- projectAddress: v.projectAddress,
- latAndLong: v.latAndLong,
- deptId: v.deptId,
- organizerUsers: v.organizerUsers,
- projectSite: v.projectSite,
- projectSiteCode: v.projectSiteCode,
- fireReason: v.fireReason,
- accidentDate: v.accidentDate,
- caseId: v.caseId,
- id: v.id,
- });
- };
- const autoSave = async () => {
- // criminal 路由不触发 fire 自动保存
- if (props.fromRoute === 'criminal') return;
- if (props.editOrShow === 'show') return;
- // if (!isValidForAutoSave()) return;
- const snapshot = getSnapshot();
- if (snapshot === lastSavedSnapshot) return;
- // 写入派生字段
- bindFire.value.accidentDate = dateFormat(accidentDate.value, "yyyy-MM-dd");
- bindFire.value.projectSiteCode = getCode(place, bindFire.value.projectSite);
- try {
- if (bindFire.value.id) {
- console.log('auto-save update', bindFire.value);
- await setFire(bindFire.value);
- if (firstReload.value != 0) {
- ElMessage.success('保存成功');
- }
- firstReload.value += 1;
- } else {
- await addFire(bindFire.value as any);
- ElMessage.success('新增成功');
- }
- lastSavedSnapshot = snapshot;
- // 保存成功后派发更新事件,供父组件同步 currentRecord
- dispatchRecordUpdate();
- // 自动保存成功后不刷新页面,也不打扰用户
- } catch (e) {
- // 自动保存失败不打断填写,可在控制台查看错误
- console.error("auto-save error", e);
- }
- };
- // 防抖触发:用户停顿一会儿再保存
- const triggerAutoSave = debounce(() => autoSave(), 800);
- // ========== criminal 自动保存(与 edit.vue 保存逻辑一致) ==========
- const isValidForAutoSaveCriminal = () => {
- const v: any = bindFire.value;
- return !!v.caseTitle && !!String(v.caseTitle).trim() && !!v.latAndLong && !!String(v.latAndLong).trim();
- };
- let lastSavedSnapshotCriminal = "";
- const getSnapshotCriminal = () => {
- const v: any = bindFire.value;
- return JSON.stringify({
- caseTitle: v.caseTitle,
- mapUrl: v.mapUrl,
- latAndLong: v.latAndLong,
- caseId: v.caseId,
- id: v.id,
- });
- };
- const autoSaveCriminal = async () => {
- if (props.fromRoute !== 'criminal') return;
- if (props.editOrShow === 'show') return;
- // if (!isValidForAutoSaveCriminal()) return;
- const snapshot = getSnapshotCriminal();
- if (snapshot === lastSavedSnapshotCriminal) return;
- try {
- if ((bindFire.value as any).caseId) {
- await setCriminalExample(bindFire.value as any);
- if (firstReload.value != 0) {
- ElMessage.success('保存成功');
- }
- firstReload.value += 1;
- } else {
- await addCriminalExample(bindFire.value as any);
- ElMessage.success('新增成功');
- }
- lastSavedSnapshotCriminal = snapshot;
- // 保存成功后派发标题更新事件,附带地图信息,供父组件同步 currentRecord
- dispatchRecordUpdate();
- } catch (e) {
- console.error('criminal auto-save error', e);
- }
- };
- const triggerAutoSaveCriminal = debounce(() => autoSaveCriminal(), 800);
- // 深度监听表单对象;日期单独监听
- watch(bindFire, () => {
- if (props.editOrShow === 'show') return;
- if (props.fromRoute === 'criminal') {
- triggerAutoSaveCriminal();
- } else {
- triggerAutoSave();
- }
- }, { deep: true });
- watch(accidentDate, () => {
- if (props.editOrShow === 'show') return;
- if (props.fromRoute !== 'criminal') triggerAutoSave();
- });
- onBeforeUnmount(() => {
- if (props.editOrShow === 'show') return;
- dispatchRecordUpdate();
- });
- // 监听来自 header 的重命名事件,并直接触发保存以更新标题
- onMounted(() => {
- const renameHandler = (evt: any) => {
- const title = evt?.detail?.title || '';
- if (!title || !String(title).trim()) return;
- if (props.fromRoute === 'criminal') {
- (bindFire.value as any).caseTitle = title;
- if (props.editOrShow !== 'show') {
- autoSaveCriminal();
- }
- } else {
- (bindFire.value as any).projectName = title;
- if (props.editOrShow !== 'show') {
- autoSave();
- }
- }
- };
- window.addEventListener('fireDetails:renameTitle', renameHandler as any);
- onUnmounted(() => {
- window.removeEventListener('fireDetails:renameTitle', renameHandler as any);
- });
- });
- defineExpose<QuiskExpose>({
- async submit() {
- if (props.editOrShow === 'show') return;
- if (props.fromRoute === 'criminal') {
- if (!bindFire.value.caseTitle || !bindFire.value.caseTitle.trim()) {
- ElMessage.error("案件名称不能为空");
- throw "案件名称不能为空";
- } else if (!bindFire.value.latAndLong || !bindFire.value.latAndLong.trim()) {
- ElMessage.error("详细地址不能为空");
- throw "详细地址不能为空!";
- }
- await (bindFire.value.caseId ? setCriminalExample(bindFire.value as any) : addCriminalExample(bindFire.value as any));
- // 保存成功后刷新页面数据
- window.location.reload()
- } else {
- if (!bindFire.value.latAndLong || !bindFire.value.latAndLong.trim()) {
- ElMessage.error("详细地址不能为空");
- throw "详细地址不能为空!";
- } else if (!bindFire.value.projectAddress || !bindFire.value.projectAddress.trim()) {
- ElMessage.error("起火地址不能为空!");
- throw "起火地址不能为空!";
- } else if (!bindFire.value.projectSn || !bindFire.value.projectSn.trim()) {
- ElMessage.error("项目编号不能为空!");
- throw "项目编号不能为空!";
- } else if (!bindFire.value.projectName || !bindFire.value.projectName.trim()) {
- ElMessage.error("起火对象不能为空!");
- throw "起火对象不能为空!";
- } else if (!bindFire.value.projectSite || !bindFire.value.projectSite.trim()) {
- ElMessage.error("起火场所不能为空!");
- throw "起火场所不能为空!";
- } else if (!bindFire.value.deptId || !bindFire.value.deptId.trim()) {
- ElMessage.error("承办单位不能为空!");
- throw "承办单位不能为空!";
- } else if (!bindFire.value.organizerUsers || !bindFire.value.organizerUsers.trim()) {
- ElMessage.error("承办人员不能为空!");
- throw "承办人员不能为空!";
- } else if (!accidentDate) {
- ElMessage.error("事故日期不能为空!");
- throw "事故日期不能为空!";
- } else if (!bindFire.value.fireReason || !bindFire.value.fireReason.trim()) {
- ElMessage.error("火灾原因不能为空!");
- throw "火灾原因不能为空!";
- }
- bindFire.value.accidentDate = dateFormat(accidentDate.value, "yyyy-MM-dd");
- bindFire.value.projectSiteCode = getCode(place, bindFire.value.projectSite);
-
- // 保存数据
- if (bindFire.value.id) {
- await setFire(bindFire.value);
- } else {
- await addFire(bindFire.value as any);
- }
- // 保存成功后,刷新fireDetails页面的数据
- window.location.reload()
- }
- },
- });
- // 打开地图/选择地址
- const searchAMapAddress = async () => {
- // 统一使用 creatMap 弹窗进行位置选择
- showMapDialog.value = true;
- };
- // 处理地图确认选择
- const handleMapConfirm = (LocationInfo: any) => {
- console.log(LocationInfo, 666)
- const {cityname, adname, address, name, location} = LocationInfo;
- bindFire.value.mapUrl = cityname + adname + address + name;
- bindFire.value.latlng = bindFire.value.latAndLong = `${location.lat},${location.lng}`;
- showMapDialog.value = false;
- }
- </script>
- <style scoped lang="scss">
- .basic-info{
- width: 100%;
- height: calc(100% - 174px);
- background: #f5f7fa;
- padding: 24px 0;
- .show-view-content{
- height: calc(100% - 20px);
- }
- .caseTitle{
- width: 60%;
- text-align: center;
- background: #FFFFFF;
- margin: 0 auto;
- padding: 48px 140px 0px 140px;
- font-size: 24px;
- }
- }
- .camera-from {
- width: 60%;
- height: calc(100% - 185px);
- background: #FFFFFF;
- margin: 0 auto;
- padding: 40px 140px 48px 140px;
- .all-content{
- height: calc(100% - 0px);
- padding-right: 60px;
- overflow: auto;
- }
- .no-scrollbar {
- overflow-y: scroll; /* 允许垂直滚动 */
- scrollbar-width: none; /* Firefox */
- -ms-overflow-style: none; /* Internet Explorer 10+ */
- }
- .no-scrollbar::-webkit-scrollbar {
- /* Chrome, Safari, Opera */
- display: none;
- }
- .form-title{
- text-align: center;
- font-family: Microsoft YaHei, Microsoft YaHei;
- font-weight: 400;
- font-size: 24px;
- color: rgba(0,0,0,0.85);
- line-height: 36px;
- margin-bottom: 44px;
- }
- .mandatory{
- .el-input-group__append button.el-button{
- border-left: 0;
- }
- }
- }
- </style>
- <style scoped lang="scss">
- .show-view {
- .info-row {
- display: flex;
- // align-items: center;
- margin-bottom: 40px;
- .label {
- width: 84px;
- color: rgba(0, 0, 0, 0.85);
- }
- .value {
- flex: 1;
- text-align: left;
- color: rgba(0, 0, 0, 0.85);
- }
- }
- }
- .camera-from{
- :deep(.el-form-item){
- margin-bottom: 40px;
- }
- :deep(.el-form-item__label){
- padding-right: 24px;
- }
- }
- </style>
|