|
|
@@ -1,995 +0,0 @@
|
|
|
-<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>
|