|
@@ -0,0 +1,995 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <scroll-view scroll-y="true" class="report-form">
|
|
|
|
|
+ <!-- 日报详情 - 日报信息 -->
|
|
|
|
|
+ <view class="report-form-content form-content">
|
|
|
|
|
+ <uni-forms
|
|
|
|
|
+ ref="reportFormRef"
|
|
|
|
|
+ labelWidth="140px"
|
|
|
|
|
+ :modelValue="formData"
|
|
|
|
|
+ :rules="formDataRules"
|
|
|
|
|
+ err-show-type="toast">
|
|
|
|
|
+ <!-- 时间节点 -->
|
|
|
|
|
+ <uni-forms-item class="form-item" :label="`${$t('ruiDu.timeNode')}:`" :required="isRequired">
|
|
|
|
|
+ <view
|
|
|
|
|
+ class="time-range-container flex-row align-center justify-end"
|
|
|
|
|
+ @click="props.formDisable ? '' : handleClickTimeRange()">
|
|
|
|
|
+ <view class="time-range-item" v-if="formData.startTime && formData.endTime">
|
|
|
|
|
+ {{ formData.startTime }} 至 {{ formData.endTime }}
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="time-range-item" v-else>
|
|
|
|
|
+ {{ selectPlaceholder }}
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </uni-forms-item>
|
|
|
|
|
+ <!-- 施工状态 -->
|
|
|
|
|
+ <uni-forms-item
|
|
|
|
|
+ class="form-item"
|
|
|
|
|
+ :label="`${$t('ruiDu.constructionStatus')}:`"
|
|
|
|
|
+ :required="isRequired"
|
|
|
|
|
+ name="rdStatus">
|
|
|
|
|
+ <uni-data-select
|
|
|
|
|
+ class="form-item-select align-center"
|
|
|
|
|
+ :clear="false"
|
|
|
|
|
+ :align="'right'"
|
|
|
|
|
+ :localdata="rdStatusRange"
|
|
|
|
|
+ :placeholder="selectPlaceholder"
|
|
|
|
|
+ :disabled="props.formDisable"
|
|
|
|
|
+ v-model="formData.rdStatus">
|
|
|
|
|
+ </uni-data-select>
|
|
|
|
|
+ </uni-forms-item>
|
|
|
|
|
+ <!-- 施工设备 -->
|
|
|
|
|
+ <uni-forms-item class="form-item" :label="`${$t('ruiDu.constructionEquipment')}:`">
|
|
|
|
|
+ <view class="flex-col">
|
|
|
|
|
+ <uni-row class="flex-row align-center justify-end">
|
|
|
|
|
+ <uni-col :span="6" class="flex-col align-center justify-end">
|
|
|
|
|
+ <button
|
|
|
|
|
+ class="mini-btn form-item-btn align-center justify-center"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ :disabled="props.formDisable"
|
|
|
|
|
+ v-if="!props.formDisable"
|
|
|
|
|
+ @click="handleClickSelectDevice">
|
|
|
|
|
+ {{ $t('device.selectDevice') }}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </uni-col>
|
|
|
|
|
+ </uni-row>
|
|
|
|
|
+ <uni-row>
|
|
|
|
|
+ <uni-easyinput
|
|
|
|
|
+ style="text-align: right"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :autoHeight="true"
|
|
|
|
|
+ :inputBorder="false"
|
|
|
|
|
+ :clearable="false"
|
|
|
|
|
+ :styles="{ disableColor: '#fff' }"
|
|
|
|
|
+ :placeholder="$t('ruiDu.unselectedEquipment')"
|
|
|
|
|
+ :disabled="true"
|
|
|
|
|
+ v-model="selectedEquipmentNames" />
|
|
|
|
|
+ </uni-row>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </uni-forms-item>
|
|
|
|
|
+ <!-- 未选择设备 -->
|
|
|
|
|
+ <uni-forms-item class="form-item" :label="`${$t('ruiDu.unselectedEquipment')}:`">
|
|
|
|
|
+ <uni-easyinput
|
|
|
|
|
+ style="text-align: right"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :autoHeight="true"
|
|
|
|
|
+ :inputBorder="false"
|
|
|
|
|
+ :clearable="false"
|
|
|
|
|
+ :styles="{ disableColor: '#fff' }"
|
|
|
|
|
+ :placeholder="' '"
|
|
|
|
|
+ :disabled="true"
|
|
|
|
|
+ v-model="unselectedEquipmentNames" />
|
|
|
|
|
+ </uni-forms-item>
|
|
|
|
|
+ <!-- 施工工艺 -->
|
|
|
|
|
+ <uni-forms-item
|
|
|
|
|
+ class="form-item"
|
|
|
|
|
+ :label="`${$t('ruiDu.constructionProcess')}:`"
|
|
|
|
|
+ :required="isRequired"
|
|
|
|
|
+ name="techniqueIds">
|
|
|
|
|
+ <uni-data-select
|
|
|
|
|
+ ref="uniDataSelect"
|
|
|
|
|
+ mode="underline"
|
|
|
|
|
+ placement="bottom"
|
|
|
|
|
+ :multiple="true"
|
|
|
|
|
+ :clear="false"
|
|
|
|
|
+ :localdata="techniqueRange"
|
|
|
|
|
+ :disabled="props.formDisable"
|
|
|
|
|
+ v-model="formData.techniqueIds">
|
|
|
|
|
+ <template v-slot:selected="{ selectedItems }" class="form-item">
|
|
|
|
|
+ <view class="slot-box flex-row align-center justify-end">
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="item in selectedItems"
|
|
|
|
|
+ :key="item.value"
|
|
|
|
|
+ class="slot-content-item selected align-center justify-start">
|
|
|
|
|
+ {{ item.text }}
|
|
|
|
|
+ <uni-icons
|
|
|
|
|
+ type="close"
|
|
|
|
|
+ size="18"
|
|
|
|
|
+ color="#909399"
|
|
|
|
|
+ v-if="!props.formDisable"
|
|
|
|
|
+ @click="removeSelectedItem(item.value)"></uni-icons>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="selectedItems.length == 0" class="slot-content-item align-center justify-start">
|
|
|
|
|
+ {{ selectPlaceholder }}
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <template v-slot:option="{ item, itemSelected }">
|
|
|
|
|
+ <view class="slot-item">
|
|
|
|
|
+ <uni-list-item class="slot-list-item flex-row align-center justify-between">
|
|
|
|
|
+ <template v-slot:body>
|
|
|
|
|
+ <text class="slot-item-text justify-start" :style="{ color: itemSelected ? '#007aff' : '#303133' }">
|
|
|
|
|
+ {{ item.text }}
|
|
|
|
|
+ </text>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <template v-slot:footer>
|
|
|
|
|
+ <uni-icons
|
|
|
|
|
+ class="align-center justify-center"
|
|
|
|
|
+ v-if="itemSelected"
|
|
|
|
|
+ type="checkmarkempty"
|
|
|
|
|
+ size="20"
|
|
|
|
|
+ color="#007aff"></uni-icons>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </uni-list-item>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <!-- 可以拦截点击事件之后自定义
|
|
|
|
|
+ <template v-slot:option="{item,itemSelected}">
|
|
|
|
|
+ <view @click.stop>
|
|
|
|
|
+ <uni-list-item showSwitch :switchChecked="itemSelected" :title="item.text" :note="item.value+''"
|
|
|
|
|
+ :disabled="item.disable" @switchChange="switchChange($event,item)"></uni-list-item>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </template> -->
|
|
|
|
|
+ <template v-slot:empty>
|
|
|
|
|
+ <view class="empty-box">
|
|
|
|
|
+ <view>{{ $t('common.noData') }}</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </uni-data-select>
|
|
|
|
|
+ </uni-forms-item>
|
|
|
|
|
+
|
|
|
|
|
+ <!--当日生产动态 -->
|
|
|
|
|
+ <uni-forms-item
|
|
|
|
|
+ class="form-item"
|
|
|
|
|
+ :label="`${$t('ruiDu.dailyProductionDynamic')}:`"
|
|
|
|
|
+ :required="isRequired"
|
|
|
|
|
+ name="productionStatus">
|
|
|
|
|
+ <uni-easyinput
|
|
|
|
|
+ style="text-align: right"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :autoHeight="true"
|
|
|
|
|
+ :inputBorder="false"
|
|
|
|
|
+ :clearable="false"
|
|
|
|
|
+ :styles="{ disableColor: '#fff' }"
|
|
|
|
|
+ :placeholder="inputPlaceholder"
|
|
|
|
|
+ :disabled="props.formDisable"
|
|
|
|
|
+ v-model="formData.productionStatus" />
|
|
|
|
|
+ </uni-forms-item>
|
|
|
|
|
+ <!-- 下步工作计划 -->
|
|
|
|
|
+ <uni-forms-item class="form-item" :label="`${$t('ruiDu.nextWorkPlan')}:`" name="nextPlan">
|
|
|
|
|
+ <uni-easyinput
|
|
|
|
|
+ style="text-align: right"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :autoHeight="true"
|
|
|
|
|
+ :inputBorder="false"
|
|
|
|
|
+ :clearable="false"
|
|
|
|
|
+ :styles="{ disableColor: '#fff' }"
|
|
|
|
|
+ :placeholder="inputPlaceholder"
|
|
|
|
|
+ :disabled="props.formDisable"
|
|
|
|
|
+ v-model="formData.nextPlan" />
|
|
|
|
|
+ </uni-forms-item>
|
|
|
|
|
+ <!-- 外租设备 -->
|
|
|
|
|
+ <uni-forms-item class="form-item" :label="`${$t('ruiDu.externalRentalEquipment')}:`" name="externalRental">
|
|
|
|
|
+ <uni-easyinput
|
|
|
|
|
+ style="text-align: right"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :autoHeight="true"
|
|
|
|
|
+ :inputBorder="false"
|
|
|
|
|
+ :clearable="false"
|
|
|
|
|
+ :styles="{ disableColor: '#fff' }"
|
|
|
|
|
+ :placeholder="inputPlaceholder"
|
|
|
|
|
+ :disabled="props.formDisable"
|
|
|
|
|
+ v-model="formData.externalRental" />
|
|
|
|
|
+ </uni-forms-item>
|
|
|
|
|
+ <!-- 故障情况 -->
|
|
|
|
|
+ <uni-forms-item class="form-item" :label="`${$t('ruiDu.faultSituation')}:`" name="malfunction">
|
|
|
|
|
+ <uni-easyinput
|
|
|
|
|
+ style="text-align: right"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :autoHeight="true"
|
|
|
|
|
+ :inputBorder="false"
|
|
|
|
|
+ :clearable="false"
|
|
|
|
|
+ :styles="{ disableColor: '#fff' }"
|
|
|
|
|
+ :placeholder="inputPlaceholder"
|
|
|
|
|
+ :disabled="props.formDisable"
|
|
|
|
|
+ v-model="formData.malfunction" />
|
|
|
|
|
+ </uni-forms-item>
|
|
|
|
|
+ <!-- 故障误工H -->
|
|
|
|
|
+ <uni-forms-item class="form-item" :label="`${$t('ruiDu.faultDowntimeH')}:`" name="faultDowntime">
|
|
|
|
|
+ <uni-easyinput
|
|
|
|
|
+ class="digit-item"
|
|
|
|
|
+ type="digit"
|
|
|
|
|
+ :inputBorder="false"
|
|
|
|
|
+ :clearable="true"
|
|
|
|
|
+ :styles="{ disableColor: '#fff' }"
|
|
|
|
|
+ :placeholder="inputPlaceholder"
|
|
|
|
|
+ :disabled="props.formDisable"
|
|
|
|
|
+ v-model="formData.faultDowntime" />
|
|
|
|
|
+ </uni-forms-item>
|
|
|
|
|
+ <!-- 附件 -->
|
|
|
|
|
+ <uni-forms-item class="form-item" :label="`${$t('ruiDu.attachment')}:`" :required="false" name="attachments">
|
|
|
|
|
+ <uni-file-picker
|
|
|
|
|
+ file-mediatype="all"
|
|
|
|
|
+ :clear="false"
|
|
|
|
|
+ :limit="9"
|
|
|
|
|
+ :auto-upload="false"
|
|
|
|
|
+ :readonly="props.formDisable"
|
|
|
|
|
+ v-model="attachmentsFileList"
|
|
|
|
|
+ @select="uploadFiles"
|
|
|
|
|
+ @delete="deleteFiles"
|
|
|
|
|
+ v-if="!props.formDisable">
|
|
|
|
|
+ <view class="file-picker-container flex-row align-end justify-end">
|
|
|
|
|
+ <view class="file-size-limit">
|
|
|
|
|
+ {{ $t('ruiDu.fileSizeLimit') }}
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <button type="primary" size="mini" class="file-picker-btn">
|
|
|
|
|
+ {{ $t('ruiDu.selectFile') }}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </uni-file-picker>
|
|
|
|
|
+ <view class="file-list flex-col justify-start" v-else>
|
|
|
|
|
+ <uni-row
|
|
|
|
|
+ class="file-item flex-row align-center justify-between"
|
|
|
|
|
+ v-for="(file, index) in attachmentsFileList"
|
|
|
|
|
+ :key="index">
|
|
|
|
|
+ <uni-col :span="20" class="file-name flex-row">{{ file.name }}</uni-col>
|
|
|
|
|
+ <uni-col :span="3" class="file-btn align-center" @click="downloadFile(file)">
|
|
|
|
|
+ {{ $t('operation.download') }}
|
|
|
|
|
+ </uni-col>
|
|
|
|
|
+ </uni-row>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </uni-forms-item>
|
|
|
|
|
+ <!-- --------------- 以下是根据施工工艺获取的工作量字段 ------------------ -->
|
|
|
|
|
+ <uni-forms-item
|
|
|
|
|
+ class="form-item"
|
|
|
|
|
+ v-for="(attr, index) in formData.extProperty"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ :label="`${attr.name}(${attr.unit}):`"
|
|
|
|
|
+ :required="attr.required == 1 && isRequired"
|
|
|
|
|
+ :name="`extProperty[${index}].actualValue`">
|
|
|
|
|
+ <uni-easyinput
|
|
|
|
|
+ v-if="attr.dataType === 'double'"
|
|
|
|
|
+ class="digit-item"
|
|
|
|
|
+ type="digit"
|
|
|
|
|
+ :inputBorder="false"
|
|
|
|
|
+ :clearable="true"
|
|
|
|
|
+ :styles="{ disableColor: '#fff' }"
|
|
|
|
|
+ :placeholder="inputPlaceholder"
|
|
|
|
|
+ :disabled="props.formDisable"
|
|
|
|
|
+ v-model="attr.actualValue" />
|
|
|
|
|
+ <uni-easyinput
|
|
|
|
|
+ v-else
|
|
|
|
|
+ style="text-align: right"
|
|
|
|
|
+ :styles="{ disableColor: '#fff' }"
|
|
|
|
|
+ :inputBorder="false"
|
|
|
|
|
+ :clearable="true"
|
|
|
|
|
+ :placeholder="inputPlaceholder"
|
|
|
|
|
+ :disabled="props.formDisable"
|
|
|
|
|
+ v-model="attr.actualValue"
|
|
|
|
|
+ :type="'textarea'"></uni-easyinput>
|
|
|
|
|
+ </uni-forms-item>
|
|
|
|
|
+ </uni-forms>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </scroll-view>
|
|
|
|
|
+ <!-- 时间范围选择器 (时:分)-->
|
|
|
|
|
+ <tpf-time-range
|
|
|
|
|
+ ref="timeRangeRef"
|
|
|
|
|
+ :startTime="startTime"
|
|
|
|
|
+ :startDefaultTime="startDefaultTime"
|
|
|
|
|
+ :endTime="endTime"
|
|
|
|
|
+ :endDefaultTime="endDefaultTime"
|
|
|
|
|
+ @timeRange="timeRange"></tpf-time-range>
|
|
|
|
|
+ <!-- 设备选择器 (穿梭框) -->
|
|
|
|
|
+ <device-transfer
|
|
|
|
|
+ ref="deviceTransferRef"
|
|
|
|
|
+ :allList="reportData.selectedDevices"
|
|
|
|
|
+ :selected="formData.deviceIds"
|
|
|
|
|
+ @confirm="handleTransferChange" />
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+ import { onLoad } from '@dcloudio/uni-app';
|
|
|
|
|
+ import { ref, reactive, computed, getCurrentInstance, onMounted, nextTick, watch } from 'vue';
|
|
|
|
|
+
|
|
|
|
|
+ // -------------------------- 引入api接口 start--------------------------
|
|
|
|
|
+ import { getRuiDuReportAttrs, updateRuiDuReport, uploadAttachmentsFile } from '@/api/ruiDu.js';
|
|
|
|
|
+ // -------------------------- 引入api接口 end --------------------------
|
|
|
|
|
+ // --------------------------引用组件-----start---------------------------
|
|
|
|
|
+ import tpfTimeRange from '@/components/tpf-time-range/tpf-time-range.vue';
|
|
|
|
|
+ import deviceTransfer from '@/components/device-transfer/index.vue';
|
|
|
|
|
+ // --------------------------引用组件-----end-----------------------------
|
|
|
|
|
+ // --------------------------引用全局变量$t-------------------------------
|
|
|
|
|
+ const { appContext } = getCurrentInstance();
|
|
|
|
|
+ const t = appContext.config.globalProperties.$t;
|
|
|
|
|
+ // --------------------------引用字典项-----------------------------------
|
|
|
|
|
+ import { useDataDictStore } from '@/store/modules/dataDict';
|
|
|
|
|
+ const { getIntDictOptions, getStrDictOptions } = useDataDictStore();
|
|
|
|
|
+
|
|
|
|
|
+ // --------------------------字典项-----------------------------------
|
|
|
|
|
+ // 施工状态
|
|
|
|
|
+ const rdStatusRange = getStrDictOptions('rdStatus').map(item => {
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ text: item.label,
|
|
|
|
|
+ };
|
|
|
|
|
+ });
|
|
|
|
|
+ console.log('🚀 ~ 施工状态:', rdStatusRange);
|
|
|
|
|
+ // 施工工艺
|
|
|
|
|
+ const techniqueRange = getIntDictOptions('rq_iot_project_technology_rd').map(item => {
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ text: item.label,
|
|
|
|
|
+ };
|
|
|
|
|
+ });
|
|
|
|
|
+ console.log('🚀 ~ 施工工艺:', techniqueRange);
|
|
|
|
|
+
|
|
|
|
|
+ // -------------------------接收父组件传递的参数--------------------------
|
|
|
|
|
+ const props = defineProps({
|
|
|
|
|
+ reportId: {
|
|
|
|
|
+ type: String,
|
|
|
|
|
+ default: '',
|
|
|
|
|
+ },
|
|
|
|
|
+ reportData: {
|
|
|
|
|
+ type: Object,
|
|
|
|
|
+ default: () => {},
|
|
|
|
|
+ },
|
|
|
|
|
+ formDisable: {
|
|
|
|
|
+ type: Boolean,
|
|
|
|
|
+ default: false, // 是否禁用表单
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // -------------------------- 生命周期函数 --------------------------
|
|
|
|
|
+ onMounted(() => {
|
|
|
|
|
+ console.log('🚀 ~ report-form onMounted ~ props:', props);
|
|
|
|
|
+ // 初始化表单数据
|
|
|
|
|
+ // formDataFormat();
|
|
|
|
|
+ });
|
|
|
|
|
+ onLoad(options => {
|
|
|
|
|
+ console.log('🚀 ~ report-form onLoad ~ options:', options);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // -------------------------- 页面变量 --------------------------
|
|
|
|
|
+ // 表单ref
|
|
|
|
|
+ const reportFormRef = ref(null);
|
|
|
|
|
+ // 选择占位符
|
|
|
|
|
+ const selectPlaceholder = computed(() => {
|
|
|
|
|
+ return props.formDisable ? ' ' : t('operation.PleaseSelect');
|
|
|
|
|
+ });
|
|
|
|
|
+ // 输入占位符
|
|
|
|
|
+ const inputPlaceholder = computed(() => {
|
|
|
|
|
+ return props.formDisable ? ' ' : t('operation.PleaseInput');
|
|
|
|
|
+ });
|
|
|
|
|
+ // 表单项是否必填 required
|
|
|
|
|
+ const isRequired = computed(() => {
|
|
|
|
|
+ return props.formDisable ? false : true;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // ---------------时间范围---------------
|
|
|
|
|
+ const timeRangeRef = ref(null);
|
|
|
|
|
+ const startTime = ref('00:00');
|
|
|
|
|
+ const startDefaultTime = ref('06:00');
|
|
|
|
|
+ const endTime = ref('24:00');
|
|
|
|
|
+ const endDefaultTime = ref('06:00');
|
|
|
|
|
+ // ------------- 施工设备 ---------------
|
|
|
|
|
+ // 设备选择器
|
|
|
|
|
+ const deviceTransferRef = ref(null);
|
|
|
|
|
+ // 已选择的设备(名称)
|
|
|
|
|
+ const selectedEquipmentNames = ref('');
|
|
|
|
|
+ // 未选择的设备(名称)
|
|
|
|
|
+ const unselectedEquipmentNames = ref('');
|
|
|
|
|
+ // 上传的附件文件列表
|
|
|
|
|
+ const attachmentsFileList = ref([]);
|
|
|
|
|
+
|
|
|
|
|
+ // 表单数据
|
|
|
|
|
+ const formData = ref({
|
|
|
|
|
+ startTime: startDefaultTime.value, //时间节点 - 开始时间
|
|
|
|
|
+ endTime: endDefaultTime.value, //时间节点 - 结束时间
|
|
|
|
|
+ rdStatus: '', //施工状态
|
|
|
|
|
+ deviceIds: [], //施工设备
|
|
|
|
|
+ techniqueIds: [], //施工工艺
|
|
|
|
|
+ productionStatus: '', //当日生产动态
|
|
|
|
|
+ attachments: [], // 附件
|
|
|
|
|
+ extProperty: [], // 扩展属性
|
|
|
|
|
+ });
|
|
|
|
|
+ console.log('🚀 ~ formData:', formData);
|
|
|
|
|
+ // 表单校验规则
|
|
|
|
|
+ const formDataBaseRules = reactive({
|
|
|
|
|
+ // 时间节点 - 开始时间
|
|
|
|
|
+ startTime: {
|
|
|
|
|
+ rules: [
|
|
|
|
|
+ {
|
|
|
|
|
+ required: true,
|
|
|
|
|
+ errorMessage: `${t('operation.PleaseSelect')}${t('ruiDu.timeNode')}`,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ // 时间节点 - 结束时间
|
|
|
|
|
+ endTime: {
|
|
|
|
|
+ rules: [
|
|
|
|
|
+ {
|
|
|
|
|
+ required: true,
|
|
|
|
|
+ errorMessage: `${t('operation.PleaseSelect')}${t('ruiDu.timeNode')}`,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ // 施工状态
|
|
|
|
|
+ rdStatus: {
|
|
|
|
|
+ rules: [
|
|
|
|
|
+ {
|
|
|
|
|
+ required: true,
|
|
|
|
|
+ errorMessage: `${t('operation.PleaseSelect')}${t('ruiDu.constructionStatus')}`,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ // 施工工艺
|
|
|
|
|
+ techniqueIds: {
|
|
|
|
|
+ rules: [
|
|
|
|
|
+ {
|
|
|
|
|
+ required: true,
|
|
|
|
|
+ errorMessage: `${t('operation.PleaseSelect')}${t('ruiDu.constructionProcess')}`,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ // 当日生产动态
|
|
|
|
|
+ productionStatus: {
|
|
|
|
|
+ rules: [
|
|
|
|
|
+ {
|
|
|
|
|
+ required: true,
|
|
|
|
|
+ errorMessage: `${t('operation.PleaseFillIn')}${t('ruiDu.dailyProductionDynamic')}`,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ // 动态计算表单校验规则
|
|
|
|
|
+ const formDataRules = computed(() => {
|
|
|
|
|
+ const rules = { ...formDataBaseRules };
|
|
|
|
|
+ // 根据施工工艺动态添加工作量属性字段的校验规则
|
|
|
|
|
+ formData.value.extProperty.forEach((attr, index) => {
|
|
|
|
|
+ if (attr.required == 1) {
|
|
|
|
|
+ rules[`extProperty[${index}].actualValue`] = {
|
|
|
|
|
+ rules: [
|
|
|
|
|
+ {
|
|
|
|
|
+ required: true,
|
|
|
|
|
+ errorMessage: `${t('operation.PleaseFillIn')}${attr.name}`,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ console.log('生成的校验规则:', rules);
|
|
|
|
|
+ return rules;
|
|
|
|
|
+ });
|
|
|
|
|
+ // -------------------------- 页面方法 --------------------------
|
|
|
|
|
+
|
|
|
|
|
+ // 表单校验方法
|
|
|
|
|
+ const validate = async () => {
|
|
|
|
|
+ // 同步最新的formDataRules(解决computed延迟)
|
|
|
|
|
+ const latestRules = formDataRules.value;
|
|
|
|
|
+ await nextTick(); // 等待DOM与规则同步
|
|
|
|
|
+ reportFormRef.value.setRules(latestRules);
|
|
|
|
|
+ console.log('基础校验通过(formDataRules已生效)');
|
|
|
|
|
+ // 调用uni-forms的validate方法进行校验
|
|
|
|
|
+ return await reportFormRef.value.validate();
|
|
|
|
|
+ };
|
|
|
|
|
+ // 表单提交
|
|
|
|
|
+ const submitForm = async () => {
|
|
|
|
|
+ console.log('🚀 ~ formDataRules:', formDataRules);
|
|
|
|
|
+ console.log('🚀 ~ submitForm ~ formData.value:', formData.value);
|
|
|
|
|
+
|
|
|
|
|
+ const formValid = await validate();
|
|
|
|
|
+ if (formValid) {
|
|
|
|
|
+ // 校验施工工艺的工作量属性字段是否填写
|
|
|
|
|
+ formData.value.extProperty.forEach(attr => {
|
|
|
|
|
+ if (attr.required == 1 && !attr.actualValue) {
|
|
|
|
|
+ uni.showToast({
|
|
|
|
|
+ title: `${t('operation.PleaseFillIn')}${attr.name}(${attr.unit})`,
|
|
|
|
|
+ icon: 'none',
|
|
|
|
|
+ });
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ // 处理表单数据
|
|
|
|
|
+ const formDataCopy = JSON.parse(JSON.stringify(formData.value));
|
|
|
|
|
+ // 处理施工工艺
|
|
|
|
|
+ formDataCopy.extProperty.forEach(attr => {
|
|
|
|
|
+ if (attr.dataType === 'double') {
|
|
|
|
|
+ attr.actualValue = Number(attr.actualValue);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ console.log('🚀 ~ submitForm ~ formDataCopy:', formDataCopy);
|
|
|
|
|
+ // 提交表单
|
|
|
|
|
+ updateRuiDuReport({
|
|
|
|
|
+ id: props.reportId,
|
|
|
|
|
+ companyId: props.reportData.companyId,
|
|
|
|
|
+ deptId: props.reportData.deptId,
|
|
|
|
|
+ ...formDataCopy,
|
|
|
|
|
+ }).then(res => {
|
|
|
|
|
+ // 提交成功
|
|
|
|
|
+ if (res.code === 0) {
|
|
|
|
|
+ uni.showToast({ title: t('operation.success'), icon: 'none' });
|
|
|
|
|
+ // 返回上一页
|
|
|
|
|
+ uni.navigateBack();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ uni.showToast({ title: res.msg, icon: 'none' });
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+ // 点击时间范围选择
|
|
|
|
|
+ const handleClickTimeRange = () => {
|
|
|
|
|
+ console.log('🚀 ~ handleClickTimeRange ~ timeRangeRef.value:', timeRangeRef.value);
|
|
|
|
|
+ // 打开时间范围选择器
|
|
|
|
|
+ timeRangeRef.value.open();
|
|
|
|
|
+ };
|
|
|
|
|
+ // 时间范围选择回调
|
|
|
|
|
+ const timeRange = data => {
|
|
|
|
|
+ console.log('🚀 ~ timeRange ~ data:', data);
|
|
|
|
|
+ formData.value.startTime = data[0];
|
|
|
|
|
+ formData.value.endTime = data[1];
|
|
|
|
|
+ };
|
|
|
|
|
+ // 初始化表单数据
|
|
|
|
|
+ const formDataFormat = () => {
|
|
|
|
|
+ // 处理时间范围
|
|
|
|
|
+ timeRangeFormat();
|
|
|
|
|
+ // 处理已选择的设备
|
|
|
|
|
+ if (props.reportData?.deviceIds) {
|
|
|
|
|
+ const { deviceIds = [] } = props.reportData || {};
|
|
|
|
|
+ handleEquipmentNames(deviceIds);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 处理其他表单数据
|
|
|
|
|
+ // 施工状态
|
|
|
|
|
+ formData.value.rdStatus = props.reportData.rdStatus || ''; //施工状态
|
|
|
|
|
+ // 施工工艺
|
|
|
|
|
+ formData.value.techniqueIds = props.reportData.techniqueIds || []; //施工工艺
|
|
|
|
|
+ // 当日生产动态
|
|
|
|
|
+ formData.value.productionStatus = props.reportData.productionStatus || ''; //当日生产动态
|
|
|
|
|
+ // 下步工作计划
|
|
|
|
|
+ formData.value.nextPlan = props.reportData.nextPlan || ''; //下步工作计划
|
|
|
|
|
+ // 外租设备
|
|
|
|
|
+ formData.value.externalRental = props.reportData.externalRental || ''; //外租设备
|
|
|
|
|
+ // 故障情况
|
|
|
|
|
+ formData.value.malfunction = props.reportData.malfunction || ''; //故障情况
|
|
|
|
|
+ // 故障误工H
|
|
|
|
|
+ formData.value.faultDowntime = props.reportData.faultDowntime || ''; //故障误工H
|
|
|
|
|
+ // 附件
|
|
|
|
|
+ formData.value.attachments = props.reportData.attachments || [];
|
|
|
|
|
+ // 展示用的文件列表
|
|
|
|
|
+ attachmentsFileList.value =
|
|
|
|
|
+ props.reportData?.attachments?.map(item => ({
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ name: item.filename,
|
|
|
|
|
+ url: item.filePath,
|
|
|
|
|
+ })) || [];
|
|
|
|
|
+ // 施工工艺对应的工作量属性字段
|
|
|
|
|
+ formData.value.extProperty = props.reportData.extProperty || [];
|
|
|
|
|
+ console.log('🚀 ~ formDataFormat ~ formData.value:', formData.value);
|
|
|
|
|
+ };
|
|
|
|
|
+ // 初始化时间范围
|
|
|
|
|
+ const timeRangeFormat = () => {
|
|
|
|
|
+ // 处理时间范围:将[8,0]转换成"08:00"
|
|
|
|
|
+ console.log('🚀 ~ timeRangeFormat ~ props:', props.reportData.startTime);
|
|
|
|
|
+ if (props.reportData.startTime) {
|
|
|
|
|
+ const startArr = props.reportData.startTime;
|
|
|
|
|
+ formData.value.startTime = `${startArr[0].toString().padStart(2, '0')}:${startArr[1]
|
|
|
|
|
+ .toString()
|
|
|
|
|
+ .padStart(2, '0')}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (props.reportData.endTime) {
|
|
|
|
|
+ const endArr = props.reportData.endTime;
|
|
|
|
|
+ formData.value.endTime = `${endArr[0].toString().padStart(2, '0')}:${endArr[1].toString().padStart(2, '0')}`;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ // 处理施工设备及未选择设备名称
|
|
|
|
|
+ const handleEquipmentNames = deviceIds => {
|
|
|
|
|
+ console.log('🚀 ~ handleEquipmentNames ~ deviceIds:', deviceIds);
|
|
|
|
|
+ formData.value.deviceIds = deviceIds || []; //施工设备
|
|
|
|
|
+ console.log('🚀 ~ handleEquipmentNames ~ formData.value.deviceIds:', formData.value.deviceIds);
|
|
|
|
|
+ const { selectedDevices = [] } = props.reportData || {};
|
|
|
|
|
+ const deviceIdSet = new Set(deviceIds);
|
|
|
|
|
+ // 已选择的设备(名称)
|
|
|
|
|
+ selectedEquipmentNames.value = selectedDevices
|
|
|
|
|
+ .filter(item => deviceIdSet.has(item.id))
|
|
|
|
|
+ .map(item => item.deviceName)
|
|
|
|
|
+ .join(',');
|
|
|
|
|
+
|
|
|
|
|
+ // 未选择的设备(名称)
|
|
|
|
|
+ unselectedEquipmentNames.value =
|
|
|
|
|
+ selectedDevices
|
|
|
|
|
+ .filter(item => !deviceIdSet.has(item.id))
|
|
|
|
|
+ .map(item => item.deviceName)
|
|
|
|
|
+ .join(',') || t('ruiDu.allEquipmentConstructed');
|
|
|
|
|
+ };
|
|
|
|
|
+ // 点击施工设备选择器
|
|
|
|
|
+ const handleClickSelectDevice = () => {
|
|
|
|
|
+ deviceTransferRef.value.open();
|
|
|
|
|
+ };
|
|
|
|
|
+ // 设备选择器回调
|
|
|
|
|
+ const handleTransferChange = selectedIds => {
|
|
|
|
|
+ console.log('🚀 ~ handleTransferChange ~ selectedIds:', selectedIds);
|
|
|
|
|
+ // 更新已选择的设备及名称
|
|
|
|
|
+ handleEquipmentNames(selectedIds);
|
|
|
|
|
+ };
|
|
|
|
|
+ // 移除已选择的施工工艺
|
|
|
|
|
+ const removeSelectedItem = value => {
|
|
|
|
|
+ console.log('🚀 ~ removeSelectedItem ~ value:', value);
|
|
|
|
|
+ formData.value.techniqueIds = formData.value.techniqueIds.filter(item => item !== value);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 根据已选择的施工工艺对应的工作量属性字段
|
|
|
|
|
+ const getWorkloadInfoByTechnique = () => {
|
|
|
|
|
+ getRuiDuReportAttrs({
|
|
|
|
|
+ techniqueIds: formData.value.techniqueIds.join(','),
|
|
|
|
|
+ }).then(res => {
|
|
|
|
|
+ console.log('🚀 ~ getWorkloadInfoByTechnique ~ res:', res);
|
|
|
|
|
+ const { data = [] } = res;
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 按 "identifier+unit" 去重:用Map保证唯一,key为拼接字段
|
|
|
|
|
+ const uniqueMap = new Map();
|
|
|
|
|
+ data.forEach(item => {
|
|
|
|
|
+ // 生成去重key(identifier和unit都存在才拼接,避免异常)
|
|
|
|
|
+ const key =
|
|
|
|
|
+ item.identifier && item.unit ? `${item.identifier}-${item.unit}` : Math.random().toString(36).slice(2, 11); // 异常情况用随机key避免重复
|
|
|
|
|
+ uniqueMap.set(key, item); // 重复key会覆盖,实现去重
|
|
|
|
|
+ });
|
|
|
|
|
+ // 去重后的数组
|
|
|
|
|
+ const uniqueData = Array.from(uniqueMap.values());
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 对比formData.extProperty,保留已有actualValue(避免覆盖用户输入)
|
|
|
|
|
+ const handledData = uniqueData.map(newItem => {
|
|
|
|
|
+ // 生成当前新项的去重key
|
|
|
|
|
+ const newKey = newItem.identifier && newItem.unit ? `${newItem.identifier}-${newItem.unit}` : '';
|
|
|
|
|
+
|
|
|
|
|
+ // 在原有extProperty中找匹配项
|
|
|
|
|
+ const oldItem = formData.value.extProperty.find(old => {
|
|
|
|
|
+ const oldKey = old.identifier && old.unit ? `${old.identifier}-${old.unit}` : '';
|
|
|
|
|
+ return newKey && oldKey && newKey === oldKey;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 有匹配项则复用原有actualValue,无则用新项的(默认空)
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...newItem,
|
|
|
|
|
+ actualValue: oldItem?.actualValue ?? newItem.actualValue,
|
|
|
|
|
+ };
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 重新赋值给formData.extProperty(更新页面展示)
|
|
|
|
|
+ formData.value.extProperty = handledData;
|
|
|
|
|
+
|
|
|
|
|
+ console.log('formData.value.extProperty :>> ', formData.value.extProperty);
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+ // 上传附件(超过50M提示并删除文件列表展示)
|
|
|
|
|
+ const uploadFiles = async event => {
|
|
|
|
|
+ console.log('🚀 ~ uploadFiles ~ event:', event);
|
|
|
|
|
+ const tempFiles = event.tempFiles || []; // 选择的临时文件列表
|
|
|
|
|
+ const maxSize = 50 * 1024 * 1024; // 50M(字节)
|
|
|
|
|
+ const overSizeFiles = []; // 存储超过50M的文件
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 筛选超过50M的文件,记录文件名
|
|
|
|
|
+ tempFiles.forEach(file => {
|
|
|
|
|
+ if (file.size > maxSize) {
|
|
|
|
|
+ overSizeFiles.push(file.name);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 有超过50M的文件:提示+从列表中移除
|
|
|
|
|
+ if (overSizeFiles.length > 0) {
|
|
|
|
|
+ // 提示信息
|
|
|
|
|
+ uni.showToast({
|
|
|
|
|
+ title: `【${overSizeFiles.join('、')}】${t('ruiDu.fileSizeLimit')}`,
|
|
|
|
|
+ icon: 'none',
|
|
|
|
|
+ duration: 3000,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 关键:从formData.attachments中移除超过50M的文件(根据文件名匹配)
|
|
|
|
|
+ formData.value.attachments = formData.value.attachments.filter(attach => !overSizeFiles.includes(attach.name));
|
|
|
|
|
+
|
|
|
|
|
+ // 兼容:强制更新组件状态(避免uni-file-picker仍展示已移除文件)
|
|
|
|
|
+ await nextTick();
|
|
|
|
|
+ return; // 终止后续上传逻辑
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 所有文件大小合法,执行正常上传逻辑(原有代码保留)
|
|
|
|
|
+ try {
|
|
|
|
|
+ for (const file of tempFiles) {
|
|
|
|
|
+ const uploadTask = await uploadAttachmentsFile(file.path, undefined);
|
|
|
|
|
+ console.log('🚀 ~ uploadFiles ~ uploadTask:', uploadTask);
|
|
|
|
|
+ if (uploadTask.code === 0) {
|
|
|
|
|
+ console.log('上传成功:', uploadTask);
|
|
|
|
|
+ const { data } = uploadTask;
|
|
|
|
|
+ // 根据返回结果更新附件信息
|
|
|
|
|
+ formData.value.attachments.push({
|
|
|
|
|
+ bizId: props.reportId,
|
|
|
|
|
+ category: 'daily_report',
|
|
|
|
|
+ filePath: data.files[0].filePath,
|
|
|
|
|
+ filename: data.files[0].name,
|
|
|
|
|
+ // fileSize: data.files[0].size,
|
|
|
|
|
+ // fileType: data.files[0].type,
|
|
|
|
|
+ remark: '',
|
|
|
|
|
+ type: 'attachment',
|
|
|
|
|
+ });
|
|
|
|
|
+ // 更新展示用的文件列表
|
|
|
|
|
+ attachmentsFileList.value.push({
|
|
|
|
|
+ ...data.files[0],
|
|
|
|
|
+ name: data.files[0].name,
|
|
|
|
|
+ url: data.files[0].filePath,
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.error('上传失败:', uploadTask);
|
|
|
|
|
+ uni.showToast({
|
|
|
|
|
+ title: `【${file.name}】${t('operation.uploadFail')}`,
|
|
|
|
|
+ icon: 'none',
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('上传异常:', err);
|
|
|
|
|
+ uni.showToast({ title: '上传异常,请重试', icon: 'none' });
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ // 删除附件
|
|
|
|
|
+ const deleteFiles = event => {
|
|
|
|
|
+ console.log('🚀 ~ deleteFiles ~ event:', event);
|
|
|
|
|
+ const { tempFile, index } = event;
|
|
|
|
|
+ // 1. 从formData.attachments中移除选中项
|
|
|
|
|
+ formData.value.attachments.splice(index, 1);
|
|
|
|
|
+ // 2. 从展示用的文件列表中移除选中项
|
|
|
|
|
+ attachmentsFileList.value.splice(index, 1);
|
|
|
|
|
+ };
|
|
|
|
|
+ // 下载文件
|
|
|
|
|
+ const downloadFile = async file => {
|
|
|
|
|
+ console.log('🚀 ~ downloadFile ~ file:', file);
|
|
|
|
|
+ const { url: fileUrl, name: fileName } = file;
|
|
|
|
|
+ if (!fileUrl) {
|
|
|
|
|
+ uni.showToast({ title: t('operation.fileUrlEmpty'), icon: 'none' });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 获取平台
|
|
|
|
|
+ const platform = uni.getSystemInfoSync().platform;
|
|
|
|
|
+ console.log('🚀 ~ downloadFile ~ platform:', platform);
|
|
|
|
|
+ // 判断平台
|
|
|
|
|
+ if (platform === 'android') {
|
|
|
|
|
+ uni.downloadFile({
|
|
|
|
|
+ url: fileUrl,
|
|
|
|
|
+ success: res => {
|
|
|
|
|
+ console.log('🚀 ~ downloadFile ~ res:', res);
|
|
|
|
|
+ if (res.statusCode === 200) {
|
|
|
|
|
+ uni.saveFile({
|
|
|
|
|
+ tempFilePath: res.tempFilePath,
|
|
|
|
|
+ success: res => {
|
|
|
|
|
+ console.log('🚀 ~ downloadFile saveFile ~ res:', res);
|
|
|
|
|
+ uni.showToast({
|
|
|
|
|
+ title: t('operation.downloadSuccess'),
|
|
|
|
|
+ icon: 'none',
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ fail: err => {
|
|
|
|
|
+ console.log('🚀 ~ downloadFile saveFile ~ err:', err);
|
|
|
|
|
+ uni.showToast({
|
|
|
|
|
+ title: t('operation.downloadFail'),
|
|
|
|
|
+ icon: 'none',
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ fail: err => {
|
|
|
|
|
+ console.log('🚀 ~ downloadFile ~ err:', err);
|
|
|
|
|
+ uni.showToast({
|
|
|
|
|
+ title: t('operation.downloadFail'),
|
|
|
|
|
+ icon: 'none',
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 2.1 处理文件名(避免特殊字符乱码,如中文、空格)
|
|
|
|
|
+ const safeFileName = decodeURIComponent(fileName || '未命名文件'); // 解码URL编码的文件名
|
|
|
|
|
+
|
|
|
|
|
+ // 2.2 创建隐藏的 <a> 标签(核心:利用 download 属性触发下载)
|
|
|
|
|
+ const link = document.createElement('a');
|
|
|
|
|
+ // 关键:设置 download 属性指定文件名(Web 端独有)
|
|
|
|
|
+ link.download = safeFileName;
|
|
|
|
|
+ // 设置文件地址(若跨域,需后端配置 CORS + Content-Disposition 响应头)
|
|
|
|
|
+ link.href = fileUrl;
|
|
|
|
|
+ // 隐藏 <a> 标签(不影响页面布局)
|
|
|
|
|
+ link.style.display = 'none';
|
|
|
|
|
+ // 将 <a> 标签添加到文档中(否则部分浏览器无法触发点击)
|
|
|
|
|
+ document.body.appendChild(link);
|
|
|
|
|
+
|
|
|
|
|
+ // 2.3 模拟点击 <a> 标签触发下载
|
|
|
|
|
+ link.click();
|
|
|
|
|
+
|
|
|
|
|
+ // 2.4 下载后清理资源(避免内存泄漏)
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ document.body.removeChild(link); // 移除 <a> 标签
|
|
|
|
|
+ URL.revokeObjectURL(link.href); // 释放 URL 资源(若使用 Blob 时必需)
|
|
|
|
|
+ }, 100);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('🚀 ~ Web 端下载失败:', err);
|
|
|
|
|
+ // 若直接通过 <a> 标签下载失败(如跨域),尝试通过 Blob 流下载(场景2兼容)
|
|
|
|
|
+ await downloadFileByBlob(fileUrl, fileName);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ // 兼容场景2:通过 Blob 流下载(解决跨域或后端返回流的情况)
|
|
|
|
|
+ const downloadFileByBlob = async (fileUrl, fileName) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 发起请求获取文件流(注意:需设置 responseType: 'blob')
|
|
|
|
|
+ const response = await fetch(fileUrl, {
|
|
|
|
|
+ method: 'GET',
|
|
|
|
|
+ headers: {
|
|
|
|
|
+ // 若需要登录态,添加 token(根据项目授权方式调整)
|
|
|
|
|
+ Authorization: `Bearer ${uni.getStorageSync('token')}`,
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ throw new Error(`请求失败: ${response.status}`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 将响应转换为 Blob 对象(根据文件类型设置 MIME,如 PDF 为 'application/pdf')
|
|
|
|
|
+ const blob = await response.blob();
|
|
|
|
|
+ // 3. 生成 Blob 临时 URL
|
|
|
|
|
+ const blobUrl = URL.createObjectURL(blob);
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 用 <a> 标签触发下载(同场景1逻辑)
|
|
|
|
|
+ const safeFileName = decodeURIComponent(fileName || '未命名文件');
|
|
|
|
|
+ const link = document.createElement('a');
|
|
|
|
|
+ link.download = safeFileName;
|
|
|
|
|
+ link.href = blobUrl;
|
|
|
|
|
+ link.style.display = 'none';
|
|
|
|
|
+ document.body.appendChild(link);
|
|
|
|
|
+ link.click();
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 清理资源
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ document.body.removeChild(link);
|
|
|
|
|
+ URL.revokeObjectURL(blobUrl); // 必须释放 Blob URL,避免内存泄漏
|
|
|
|
|
+ uni.hideLoading();
|
|
|
|
|
+ // uni.showToast({ title: t("operation.downloadSuccess"), icon: "success" });
|
|
|
|
|
+ }, 100);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('🚀 ~ Blob 下载失败:', err);
|
|
|
|
|
+ uni.hideLoading();
|
|
|
|
|
+ // uni.showToast({ title: t("operation.downloadFail"), icon: "error" });
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ // -------------------------- 监听 --------------------------
|
|
|
|
|
+ // 监听reportData变化
|
|
|
|
|
+ watch(
|
|
|
|
|
+ () => props.reportData,
|
|
|
|
|
+ (newVal, oldVal) => {
|
|
|
|
|
+ console.log('监听reportData变化 ~ newVal:', newVal);
|
|
|
|
|
+ console.log('🚀监听reportData变化 ~ oldVal:', oldVal);
|
|
|
|
|
+ if (newVal?.id) {
|
|
|
|
|
+ // 初始化表单数据
|
|
|
|
|
+ formDataFormat();
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ { deep: true, immediate: true }
|
|
|
|
|
+ );
|
|
|
|
|
+ // 监听施工工艺变化
|
|
|
|
|
+ watch(
|
|
|
|
|
+ () => formData.value.techniqueIds,
|
|
|
|
|
+ (newVal, oldVal) => {
|
|
|
|
|
+ console.log('监听施工工艺变化 ~ newVal:', newVal);
|
|
|
|
|
+ console.log('🚀监听施工工艺变化 ~ oldVal:', oldVal);
|
|
|
|
|
+ if (newVal.length) {
|
|
|
|
|
+ // 根据已选择的施工工艺对应的工作量属性字段
|
|
|
|
|
+ getWorkloadInfoByTechnique();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 清空工作量属性字段
|
|
|
|
|
+ formData.value.extProperty = [];
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ { deep: true, immediate: true }
|
|
|
|
|
+ );
|
|
|
|
|
+ // -------------------------- 暴露给父组件的外部方法 --------------------------
|
|
|
|
|
+ defineExpose({
|
|
|
|
|
+ submitForm,
|
|
|
|
|
+ });
|
|
|
|
|
+ // -------------------------- 事件派发 --------------------------
|
|
|
|
|
+ const emit = defineEmits([]);
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+ @import '@/style/work-order-form.scss';
|
|
|
|
|
+ .report-form {
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ }
|
|
|
|
|
+ :deep(.uni-textarea-textarea:disabled),
|
|
|
|
|
+ :deep(.uni-input-input:disabled) {
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ }
|
|
|
|
|
+ :deep(.uni-date-x) {
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ }
|
|
|
|
|
+ .digit-item {
|
|
|
|
|
+ text-align: right;
|
|
|
|
|
+ :deep(.uni-easyinput__content-input) {
|
|
|
|
|
+ padding-right: 10px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .time-range-item {
|
|
|
|
|
+ margin: 10px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .form-item-btn {
|
|
|
|
|
+ margin: 10px 0 0 0;
|
|
|
|
|
+ width: 60px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ }
|
|
|
|
|
+ .form-item-select {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+ .slot-box {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ }
|
|
|
|
|
+ .slot-content-item {
|
|
|
|
|
+ &.selected {
|
|
|
|
|
+ background: #f4f4f5;
|
|
|
|
|
+ // color: #909399;
|
|
|
|
|
+ margin: 5px;
|
|
|
|
|
+ padding: 5px;
|
|
|
|
|
+ border-radius: 3px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ .slot-item-text {
|
|
|
|
|
+ width: 90%;
|
|
|
|
|
+ }
|
|
|
|
|
+ .file-picker-container {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+ .file-picker-btn {
|
|
|
|
|
+ margin-left: unset;
|
|
|
|
|
+ margin-right: unset;
|
|
|
|
|
+ }
|
|
|
|
|
+ .file-size-limit {
|
|
|
|
|
+ font-size: 10px;
|
|
|
|
|
+ color: #ff4500;
|
|
|
|
|
+ padding: 0 10px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .file-list {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ .file-item {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ padding: 8px 5px;
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+ border: 1px solid #f5f5f5;
|
|
|
|
|
+ // 关键1:允许flex容器换行,适应文字高度
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ // 关键2:设置对齐方式,避免换行后按钮错位
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ .file-name {
|
|
|
|
|
+ margin-right: 8px;
|
|
|
|
|
+ // 关键3:允许文字换行
|
|
|
|
|
+ white-space: normal;
|
|
|
|
|
+ // 关键4:设置换行规则(中文按字换行,英文按词换行)
|
|
|
|
|
+ word-wrap: break-word;
|
|
|
|
|
+ word-break: break-all;
|
|
|
|
|
+ }
|
|
|
|
|
+ .file-btn {
|
|
|
|
|
+ color: #004098;
|
|
|
|
|
+ min-width: max-content;
|
|
|
|
|
+ // 关键5:按钮垂直居中(即使文字换行,按钮也居中)
|
|
|
|
|
+ align-self: center;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+</style>
|