Commit 7b1f488f9c67e39bceb23c97b9d5c7379e937cb9
1 parent
d768bdcf
封装下图片上传
Showing
7 changed files
with
1039 additions
and
504 deletions
common/utils/useUploadImgs.ts
0 → 100644
| 1 | +import { ref, type Ref } from 'vue' | ||
| 2 | +import { uploadImages } from '@/common/utils/upload' | ||
| 3 | +import type { UniFormRef } from '@/uni_modules/uview-plus/types' | ||
| 4 | + | ||
| 5 | +// 定义上传配置类型 | ||
| 6 | +export interface UploadImgConfig { | ||
| 7 | + maxCount?: number // 最大上传数量,默认3 | ||
| 8 | + uploadText?: string // 上传按钮文案 | ||
| 9 | + sizeType?: UniApp.UploadFileOption['sizeType'] // 图片压缩类型 | ||
| 10 | + formRef: Ref<UniFormRef | null> // 表单ref,用于校验 | ||
| 11 | + fieldName: string // 表单校验字段名(如problemImgs) | ||
| 12 | +} | ||
| 13 | + | ||
| 14 | +// 定义图片项类型 | ||
| 15 | +export interface UploadImgItem { | ||
| 16 | + url: string | ||
| 17 | + status: 'uploading' | 'success' | 'failed' | ||
| 18 | + message: string | ||
| 19 | + [key: string]: any // 兼容其他字段 | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +/** | ||
| 23 | + * 图片上传组合式函数(支持多实例复用) | ||
| 24 | + * @param config 上传配置 | ||
| 25 | + * @returns 上传相关方法和状态 | ||
| 26 | + */ | ||
| 27 | +export function useUploadImgs(config: UploadImgConfig) { | ||
| 28 | + // 默认配置 | ||
| 29 | + const defaultConfig = { | ||
| 30 | + maxCount: 3, | ||
| 31 | + uploadText: '选择图片', | ||
| 32 | + sizeType: ['compressed'] as UniApp.UploadFileOption['sizeType'], | ||
| 33 | + ...config | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + // 图片列表 | ||
| 37 | + const imgList = ref<UploadImgItem[]>([]) | ||
| 38 | + | ||
| 39 | + /** | ||
| 40 | + * 删除图片 | ||
| 41 | + */ | ||
| 42 | + const deleteImg = (event: { index: number }) => { | ||
| 43 | + imgList.value.splice(event.index, 1) | ||
| 44 | + // 删除后重新校验 | ||
| 45 | + defaultConfig.formRef.value?.validateField(defaultConfig.fieldName) | ||
| 46 | + uni.showToast({ title: '图片删除成功', icon: 'success' }) | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + /** | ||
| 50 | + * 上传图片 | ||
| 51 | + */ | ||
| 52 | + const uploadImgs = async (event: { file: UniApp.ChooseImageSuccessCallbackResult | UniApp.ChooseImageSuccessCallbackResult[] }) => { | ||
| 53 | + const fileList = Array.isArray(event.file) ? event.file : [event.file] | ||
| 54 | + const targetImgList = imgList.value | ||
| 55 | + | ||
| 56 | + // 过滤超出最大数量的图片 | ||
| 57 | + if (targetImgList.length + fileList.length > defaultConfig.maxCount) { | ||
| 58 | + uni.showToast({ title: `最多只能上传${defaultConfig.maxCount}张图片`, icon: 'none' }) | ||
| 59 | + return | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + const filePaths = fileList.map(item => item.url) | ||
| 63 | + const tempItems = fileList.map(item => ({ | ||
| 64 | + ...item, | ||
| 65 | + status: 'uploading' as const, | ||
| 66 | + message: '上传中' | ||
| 67 | + })) | ||
| 68 | + const startIndex = targetImgList.length | ||
| 69 | + targetImgList.push(...tempItems) | ||
| 70 | + | ||
| 71 | + try { | ||
| 72 | + const uploadResultUrls = await uploadImages({ | ||
| 73 | + filePaths: filePaths, | ||
| 74 | + ignoreError: true | ||
| 75 | + }) | ||
| 76 | + | ||
| 77 | + // 更新上传成功的图片 | ||
| 78 | + uploadResultUrls.forEach((url, index) => { | ||
| 79 | + if (targetImgList[startIndex + index]) { | ||
| 80 | + targetImgList.splice(startIndex + index, 1, { | ||
| 81 | + ...fileList[index], | ||
| 82 | + status: 'success' as const, | ||
| 83 | + message: '', | ||
| 84 | + url: url | ||
| 85 | + }) | ||
| 86 | + } | ||
| 87 | + }) | ||
| 88 | + | ||
| 89 | + // 处理上传失败的图片 | ||
| 90 | + if (uploadResultUrls.length < fileList.length) { | ||
| 91 | + const failCount = fileList.length - uploadResultUrls.length | ||
| 92 | + for (let i = uploadResultUrls.length; i < fileList.length; i++) { | ||
| 93 | + if (targetImgList[startIndex + i]) { | ||
| 94 | + targetImgList.splice(startIndex + i, 1, { | ||
| 95 | + ...fileList[i], | ||
| 96 | + status: 'failed' as const, | ||
| 97 | + message: '上传失败' | ||
| 98 | + }) | ||
| 99 | + } | ||
| 100 | + } | ||
| 101 | + uni.showToast({ title: `成功上传${uploadResultUrls.length}张,失败${failCount}张`, icon: 'none' }) | ||
| 102 | + } else { | ||
| 103 | + uni.showToast({ title: `成功上传${fileList.length}张图片`, icon: 'success' }) | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + // 上传完成后校验 | ||
| 107 | + defaultConfig.formRef.value?.validateField(defaultConfig.fieldName) | ||
| 108 | + } catch (err) { | ||
| 109 | + console.error(`【${defaultConfig.fieldName}】图片上传失败:`, err) | ||
| 110 | + // 标记所有上传失败 | ||
| 111 | + for (let i = 0; i < fileList.length; i++) { | ||
| 112 | + if (targetImgList[startIndex + i]) { | ||
| 113 | + targetImgList.splice(startIndex + i, 1, { | ||
| 114 | + ...fileList[i], | ||
| 115 | + status: 'failed' as const, | ||
| 116 | + message: '上传失败' | ||
| 117 | + }) | ||
| 118 | + } | ||
| 119 | + } | ||
| 120 | + uni.showToast({ title: '图片上传失败,请重试', icon: 'none' }) | ||
| 121 | + // 上传失败后校验 | ||
| 122 | + defaultConfig.formRef.value?.validateField(defaultConfig.fieldName) | ||
| 123 | + } | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + /** | ||
| 127 | + * 获取成功上传的图片URL列表 | ||
| 128 | + */ | ||
| 129 | + const getSuccessImgUrls = () => { | ||
| 130 | + return imgList.value.filter(item => item.status === 'success').map(item => item.url) | ||
| 131 | + } | ||
| 132 | + | ||
| 133 | + /** | ||
| 134 | + * 图片校验规则(供表单使用) | ||
| 135 | + */ | ||
| 136 | + const imgValidateRule = { | ||
| 137 | + required: true, | ||
| 138 | + message: `请上传${defaultConfig.uploadText.replace('选择', '')}`, | ||
| 139 | + trigger: 'change', | ||
| 140 | + validator: (rule: any, value: any, callback: (error?: Error) => void) => { | ||
| 141 | + const hasSuccessImg = imgList.value.some(item => item.status === 'success') | ||
| 142 | + hasSuccessImg ? callback() : callback(new Error(`请上传至少1张${defaultConfig.uploadText.replace('选择', '')}`)) | ||
| 143 | + } | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + return { | ||
| 147 | + imgList, | ||
| 148 | + uploadImgs, | ||
| 149 | + deleteImg, | ||
| 150 | + getSuccessImgUrls, | ||
| 151 | + imgValidateRule, | ||
| 152 | + uploadConfig: defaultConfig | ||
| 153 | + } | ||
| 154 | +} | ||
| 0 | \ No newline at end of file | 155 | \ No newline at end of file |
components/upload-image/upload-image.vue
| 1 | <template> | 1 | <template> |
| 2 | - <view class="upload-images-container"> | ||
| 3 | - <!-- 已上传图片预览(uView Album组件) --> | ||
| 4 | - <up-album | ||
| 5 | - v-if="imageList.length > 0" | ||
| 6 | - :list="imageList" | ||
| 7 | - :max-count="maxCount" | ||
| 8 | - :show-delete="showDelete" | ||
| 9 | - @delete="handleDelete" | ||
| 10 | - @preview="handlePreview" | ||
| 11 | - :border-radius="8" | ||
| 12 | - :column="column" | ||
| 13 | - :gap="15" | ||
| 14 | - ></up-album> | ||
| 15 | 2 | ||
| 16 | - <!-- 上传按钮 --> | ||
| 17 | - <up-upload | ||
| 18 | - class="upload-btn" | ||
| 19 | - :disabled="imageList.length >= maxCount" | ||
| 20 | - :multiple="true" | ||
| 21 | - :max-count="maxCount - imageList.length" | ||
| 22 | - @after-read="handleAfterRead" | ||
| 23 | - :previewFullImage="true" | ||
| 24 | - > | ||
| 25 | - <view class="upload-btn-inner"> | ||
| 26 | - <up-icon name="plus" color="#999" size="24"></up-icon> | ||
| 27 | - <text class="upload-tips" v-if="imageList.length < maxCount">上传图片</text> | ||
| 28 | - <text class="upload-tips" v-else>已达上限</text> | ||
| 29 | - </view> | ||
| 30 | - </up-upload> | ||
| 31 | - </view> | ||
| 32 | </template> | 3 | </template> |
| 33 | 4 | ||
| 34 | <script setup lang="ts"> | 5 | <script setup lang="ts"> |
| 35 | -import { ref, watch, defineProps, defineEmits, withDefaults } from 'vue'; | ||
| 36 | -import { uploadImages } from '@/common/utils/upload.js'; | ||
| 37 | 6 | ||
| 38 | - | ||
| 39 | -// 定义Props | ||
| 40 | -const props = withDefaults( | ||
| 41 | - defineProps<{ | ||
| 42 | - // 已上传图片列表(双向绑定) | ||
| 43 | - value: string[]; | ||
| 44 | - // 最大上传数量 | ||
| 45 | - maxCount?: number; | ||
| 46 | - // 是否显示删除按钮 | ||
| 47 | - showDelete?: boolean; | ||
| 48 | - // 相册列数 | ||
| 49 | - column?: number; | ||
| 50 | - // 上传文件key名 | ||
| 51 | - fileKey?: string; | ||
| 52 | - // 自定义请求头(覆盖默认) | ||
| 53 | - header?: Record<string, string>; | ||
| 54 | - // 上传失败是否继续(多图时) | ||
| 55 | - ignoreError?: boolean; | ||
| 56 | - }>(), | ||
| 57 | - { | ||
| 58 | - maxCount: 9, | ||
| 59 | - showDelete: true, | ||
| 60 | - column: 3, | ||
| 61 | - fileKey: 'file', | ||
| 62 | - ignoreError: true | ||
| 63 | - } | ||
| 64 | -); | ||
| 65 | - | ||
| 66 | -// 定义Emits | ||
| 67 | -const emit = defineEmits<{ | ||
| 68 | - // 双向绑定更新图片列表 | ||
| 69 | - (e: 'update:value', val: string[]): void; | ||
| 70 | - // 单张/多张上传成功 | ||
| 71 | - (e: 'upload-success', urls: string[]): void; | ||
| 72 | - // 上传失败 | ||
| 73 | - (e: 'upload-error', err: any): void; | ||
| 74 | - // 删除图片 | ||
| 75 | - (e: 'delete-image', index: number): void; | ||
| 76 | - // 预览图片 | ||
| 77 | - (e: 'preview-image', index: number): void; | ||
| 78 | -}>(); | ||
| 79 | - | ||
| 80 | -// 内部维护的图片列表 | ||
| 81 | -const imageList = ref([...props.value]); | ||
| 82 | - | ||
| 83 | -// 监听父组件传入的value变化 | ||
| 84 | -watch( | ||
| 85 | - () => props.value, | ||
| 86 | - (newVal) => { | ||
| 87 | - imageList.value = [...newVal]; | ||
| 88 | - }, | ||
| 89 | - { immediate: true } | ||
| 90 | -); | ||
| 91 | - | ||
| 92 | -// 监听内部列表变化,同步给父组件 | ||
| 93 | -watch( | ||
| 94 | - () => imageList.value, | ||
| 95 | - (newVal) => { | ||
| 96 | - emit('update:value', [...newVal]); | ||
| 97 | - }, | ||
| 98 | - { deep: true } | ||
| 99 | -); | ||
| 100 | - | ||
| 101 | -/** | ||
| 102 | - * 选择图片后触发(上传核心逻辑) | ||
| 103 | - */ | ||
| 104 | -const handleAfterRead = async (event: any) => { | ||
| 105 | - // 获取选择的图片临时路径 | ||
| 106 | - const filePaths = event.file.map((item: any) => item.url); | ||
| 107 | - | ||
| 108 | - try { | ||
| 109 | - uni.showLoading({ title: '上传中...' }); | ||
| 110 | - // 调用公共上传方法 | ||
| 111 | - const uploadUrls = await uploadImages({ | ||
| 112 | - filePaths, | ||
| 113 | - fileKey: props.fileKey, | ||
| 114 | - header: props.header, | ||
| 115 | - ignoreError: props.ignoreError | ||
| 116 | - }); | ||
| 117 | - | ||
| 118 | - if (uploadUrls.length > 0) { | ||
| 119 | - // 合并到已上传列表 | ||
| 120 | - imageList.value = [...imageList.value, ...uploadUrls]; | ||
| 121 | - // 通知父组件上传成功 | ||
| 122 | - emit('upload-success', uploadUrls); | ||
| 123 | - uni.showToast({ title: `成功上传${uploadUrls.length}张图片`, icon: 'success' }); | ||
| 124 | - } | ||
| 125 | - } catch (err) { | ||
| 126 | - emit('upload-error', err); | ||
| 127 | - console.error('图片上传失败:', err); | ||
| 128 | - } finally { | ||
| 129 | - uni.hideLoading(); | ||
| 130 | - } | ||
| 131 | -}; | ||
| 132 | - | ||
| 133 | -/** | ||
| 134 | - * 删除图片 | ||
| 135 | - */ | ||
| 136 | -const handleDelete = (index: number) => { | ||
| 137 | - imageList.value.splice(index, 1); | ||
| 138 | - emit('delete-image', index); | ||
| 139 | - uni.showToast({ title: '删除成功', icon: 'success' }); | ||
| 140 | -}; | ||
| 141 | - | ||
| 142 | -/** | ||
| 143 | - * 预览图片 | ||
| 144 | - */ | ||
| 145 | -const handlePreview = (index: number) => { | ||
| 146 | - emit('preview-image', index); | ||
| 147 | - // 原生预览(可选) | ||
| 148 | - uni.previewImage({ | ||
| 149 | - urls: imageList.value, | ||
| 150 | - current: imageList.value[index] | ||
| 151 | - }); | ||
| 152 | -}; | ||
| 153 | </script> | 7 | </script> |
| 154 | 8 | ||
| 155 | <style lang="scss" scoped> | 9 | <style lang="scss" scoped> |
| 156 | -.upload-images-container { | ||
| 157 | - padding: 10rpx 0; | ||
| 158 | -} | ||
| 159 | - | ||
| 160 | -// 上传按钮样式 | ||
| 161 | -.upload-btn { | ||
| 162 | - margin-top: 15rpx; | ||
| 163 | - width: 200rpx; | ||
| 164 | - height: 200rpx; | ||
| 165 | - border: 1px dashed #e5e5e5; | ||
| 166 | - border-radius: 8rpx; | ||
| 167 | - display: flex; | ||
| 168 | - align-items: center; | ||
| 169 | - justify-content: center; | ||
| 170 | - background-color: #f9f9f9; | ||
| 171 | - | ||
| 172 | - .upload-btn-inner { | ||
| 173 | - display: flex; | ||
| 174 | - flex-direction: column; | ||
| 175 | - align-items: center; | ||
| 176 | - justify-content: center; | ||
| 177 | - } | ||
| 178 | - | ||
| 179 | - .upload-tips { | ||
| 180 | - font-size: 24rpx; | ||
| 181 | - color: #999; | ||
| 182 | - margin-top: 10rpx; | ||
| 183 | - } | ||
| 184 | -} | ||
| 185 | - | ||
| 186 | -// 适配Album组件样式 | ||
| 187 | -:deep(.u-album__item) { | ||
| 188 | - border-radius: 8rpx !important; | ||
| 189 | - overflow: hidden; | ||
| 190 | -} | ||
| 191 | 10 | ||
| 192 | -:deep(.u-album__delete) { | ||
| 193 | - background-color: rgba(0, 0, 0, 0.5) !important; | ||
| 194 | - border-radius: 50% !important; | ||
| 195 | - width: 40rpx !important; | ||
| 196 | - height: 40rpx !important; | ||
| 197 | - top: 0 !important; | ||
| 198 | - right: 0 !important; | ||
| 199 | -} | ||
| 200 | </style> | 11 | </style> |
| 201 | \ No newline at end of file | 12 | \ No newline at end of file |
pages-sub/daily/maintain-manage/add-record.vue
| 1 | <template> | 1 | <template> |
| 2 | - <view class="page-container"> | ||
| 3 | - <view class="inspect-form-content commonPageLRpadding"> | 2 | + <view class="u-page"> |
| 3 | + <view class="work-order-form-content commonPageLRpadding"> | ||
| 4 | <up-form | 4 | <up-form |
| 5 | label-position="left" | 5 | label-position="left" |
| 6 | - :model="inspectForm" | ||
| 7 | - ref="inspectFormRef" | ||
| 8 | - labelWidth="140rpx" | 6 | + :model="workOrderForm" |
| 7 | + ref="workOrderFormRef" | ||
| 8 | + labelWidth="160rpx" | ||
| 9 | > | 9 | > |
| 10 | - <!-- 1. 巡查描述(文本域) --> | 10 | + <!-- 1. 工单位置(地图选择) --> |
| 11 | <up-form-item | 11 | <up-form-item |
| 12 | - prop="content" | ||
| 13 | - class="form-item" | 12 | + label="工单位置" |
| 13 | + prop="workLocation" | ||
| 14 | + border-bottom | ||
| 15 | + required | ||
| 16 | + @click="chooseWorkLocation(); hideKeyboard()" | ||
| 14 | > | 17 | > |
| 15 | - <up-textarea | ||
| 16 | - v-model="inspectForm.remark" | ||
| 17 | - placeholder="请输入巡查描述" | ||
| 18 | - maxlength="200" | ||
| 19 | - count | ||
| 20 | - ></up-textarea> | 18 | + <up-input |
| 19 | + v-model="workOrderForm.workLocation" | ||
| 20 | + border="none" | ||
| 21 | + readonly | ||
| 22 | + suffix-icon="map-fill" | ||
| 23 | + placeholder="点击选择工单位置" | ||
| 24 | + ></up-input> | ||
| 21 | </up-form-item> | 25 | </up-form-item> |
| 22 | 26 | ||
| 23 | - <!-- 2. 上传图片 --> | 27 | + <!-- 2. 道路名称(下拉框) --> |
| 24 | <up-form-item | 28 | <up-form-item |
| 25 | - label="上传图片" | ||
| 26 | - prop="images" | 29 | + label="道路名称" |
| 30 | + prop="roadName" | ||
| 31 | + border-bottom | ||
| 27 | required | 32 | required |
| 33 | + @click="workOrderForm.workLocation ? (showRoadName = true, hideKeyboard()) : uni.showToast({title: '请先选择工单位置', icon: 'none'})" | ||
| 34 | + > | ||
| 35 | + <up-input | ||
| 36 | + v-model="workOrderForm.roadName" | ||
| 37 | + disabled | ||
| 38 | + disabled-color="#ffffff" | ||
| 39 | + placeholder="请先选择工单位置" | ||
| 40 | + border="none" | ||
| 41 | + :placeholder-style="workOrderForm.workLocation ? '' : 'color:#999;'" | ||
| 42 | + ></up-input> | ||
| 43 | + <template #right> | ||
| 44 | + <up-icon name="arrow-right" size="16" :color="workOrderForm.workLocation ? '#333' : '#999'"></up-icon> | ||
| 45 | + </template> | ||
| 46 | + </up-form-item> | ||
| 47 | + | ||
| 48 | + <!-- 3. 工单名称(下拉框) --> | ||
| 49 | + <up-form-item | ||
| 50 | + label="工单名称" | ||
| 51 | + prop="orderName" | ||
| 28 | border-bottom | 52 | border-bottom |
| 29 | - class="form-item" | 53 | + required |
| 54 | + @click="showOrderName = true; hideKeyboard()" | ||
| 55 | + > | ||
| 56 | + <up-input | ||
| 57 | + v-model="workOrderForm.orderName" | ||
| 58 | + disabled | ||
| 59 | + disabled-color="#ffffff" | ||
| 60 | + placeholder="请选择工单名称" | ||
| 61 | + border="none" | ||
| 62 | + ></up-input> | ||
| 63 | + <template #right> | ||
| 64 | + <up-icon name="arrow-right" size="16"></up-icon> | ||
| 65 | + </template> | ||
| 66 | + </up-form-item> | ||
| 67 | + | ||
| 68 | + <!-- 4. 情况描述(文本域) --> | ||
| 69 | + <up-form-item | ||
| 70 | + label="情况描述" | ||
| 71 | + prop="problemDesc" | ||
| 72 | + required | ||
| 30 | > | 73 | > |
| 74 | + <up-textarea | ||
| 75 | + placeholder="请输入情况描述(最多200字)" | ||
| 76 | + v-model="workOrderForm.problemDesc" | ||
| 77 | + count | ||
| 78 | + maxlength="200" | ||
| 79 | + rows="4" | ||
| 80 | + @blur="() => workOrderFormRef.value?.validateField('problemDesc')" | ||
| 81 | + ></up-textarea> | ||
| 82 | + </up-form-item> | ||
| 83 | + | ||
| 84 | + <!-- 问题照片(复用公共上传) --> | ||
| 85 | + <up-form-item label="问题照片" prop="problemImgs" required> | ||
| 31 | <up-upload | 86 | <up-upload |
| 32 | - :file-list="imagesList" | ||
| 33 | - @after-read="(event) => uploadImgs(event)" | ||
| 34 | - @delete="(event) => deleteImg(event)" | ||
| 35 | - @on-exceed="handleExceed" | 87 | + :file-list="problemImgs.imgList" |
| 88 | + @after-read="problemImgs.uploadImgs" | ||
| 89 | + @delete="problemImgs.deleteImg" | ||
| 36 | multiple | 90 | multiple |
| 37 | - :max-count="3" | ||
| 38 | - upload-text="选择图片" | ||
| 39 | - del-color="#ff4d4f" | ||
| 40 | - class="upload-wrap" | 91 | + :max-count="problemImgs.uploadConfig.maxCount" |
| 92 | + :upload-text="problemImgs.uploadConfig.uploadText" | ||
| 93 | + :sizeType="problemImgs.uploadConfig.sizeType" | ||
| 41 | ></up-upload> | 94 | ></up-upload> |
| 42 | </up-form-item> | 95 | </up-form-item> |
| 43 | 96 | ||
| 44 | - <!-- 3. 完成进度(滑块) --> | ||
| 45 | - <up-form-item | ||
| 46 | - label="完成进度" | ||
| 47 | - prop="progress" | 97 | + <!-- 完成照片(复用公共上传) --> |
| 98 | + <up-form-item label="完成照片" prop="completeImgs" required class="mt-20"> | ||
| 99 | + <up-upload | ||
| 100 | + :file-list="completeImgs.imgList" | ||
| 101 | + @after-read="completeImgs.uploadImgs" | ||
| 102 | + @delete="completeImgs.deleteImg" | ||
| 103 | + multiple | ||
| 104 | + :max-count="completeImgs.uploadConfig.maxCount" | ||
| 105 | + :upload-text="completeImgs.uploadConfig.uploadText" | ||
| 106 | + :sizeType="completeImgs.uploadConfig.sizeType" | ||
| 107 | + ></up-upload> | ||
| 108 | + </up-form-item> | ||
| 48 | 109 | ||
| 49 | - class="form-item" | ||
| 50 | - > | ||
| 51 | - <view class="progress-wrap"> | ||
| 52 | - <up-slider | ||
| 53 | - v-model="inspectForm.progress" | ||
| 54 | - :min="initProgress" | ||
| 55 | - :max="100" | ||
| 56 | - active-color="#1989fa" | ||
| 57 | - inactive-color="#e5e5e5" | ||
| 58 | - block-size="24" | ||
| 59 | - :showValue="true" | ||
| 60 | - class="progress-slider" | ||
| 61 | - @changing="handleProgressChange" | ||
| 62 | - ></up-slider> | ||
| 63 | - </view> | 110 | + <!-- 处理结果(不必填) --> |
| 111 | + <up-form-item label="处理结果" prop="handleResult" class="mt-20"> | ||
| 112 | + <up-textarea | ||
| 113 | + placeholder="请输入处理结果描述(最多200字,选填)" | ||
| 114 | + v-model="workOrderForm.handleResult" | ||
| 115 | + count | ||
| 116 | + maxlength="200" | ||
| 117 | + rows="4" | ||
| 118 | + @blur="() => workOrderFormRef.value?.validateField('handleResult')" | ||
| 119 | + ></up-textarea> | ||
| 64 | </up-form-item> | 120 | </up-form-item> |
| 65 | </up-form> | 121 | </up-form> |
| 66 | </view> | 122 | </view> |
| @@ -69,269 +125,271 @@ | @@ -69,269 +125,271 @@ | ||
| 69 | <view class="fixed-bottom-btn-wrap"> | 125 | <view class="fixed-bottom-btn-wrap"> |
| 70 | <up-button | 126 | <up-button |
| 71 | type="primary" | 127 | type="primary" |
| 72 | - text="提交" | ||
| 73 | - @click="submitInspect" | ||
| 74 | - :style="{ width: '100%', height: '88rpx', fontSize: '32rpx', borderRadius: 0 }" | 128 | + text="提交工单" |
| 129 | + @click="submitWorkOrder" | ||
| 75 | ></up-button> | 130 | ></up-button> |
| 76 | </view> | 131 | </view> |
| 132 | + | ||
| 133 | + <!-- 道路名称下拉弹窗 --> | ||
| 134 | + <up-action-sheet | ||
| 135 | + :show="showRoadName" | ||
| 136 | + :actions="roadNameList" | ||
| 137 | + title="请选择道路名称" | ||
| 138 | + @close="showRoadName = false" | ||
| 139 | + @select="handleRoadNameSelect" | ||
| 140 | + ></up-action-sheet> | ||
| 141 | + | ||
| 142 | + <!-- 工单名称下拉弹窗 --> | ||
| 143 | + <up-action-sheet | ||
| 144 | + :show="showOrderName" | ||
| 145 | + :actions="orderNameList" | ||
| 146 | + title="请选择工单名称" | ||
| 147 | + @close="showOrderName = false" | ||
| 148 | + @select="handleOrderNameSelect" | ||
| 149 | + ></up-action-sheet> | ||
| 77 | </view> | 150 | </view> |
| 78 | </template> | 151 | </template> |
| 79 | 152 | ||
| 80 | <script setup lang="ts"> | 153 | <script setup lang="ts"> |
| 81 | -import { ref } from 'vue' | 154 | +import { ref, reactive } from 'vue' |
| 155 | +import { onReady, onShow } from '@dcloudio/uni-app' // 从uni-app导入小程序生命周期 | ||
| 82 | import type { UniFormRef } from '@/uni_modules/uview-plus/types' | 156 | import type { UniFormRef } from '@/uni_modules/uview-plus/types' |
| 83 | -const inspectFormRef = ref<UniFormRef>(null) | ||
| 84 | -</script> | 157 | +import { useUploadImgs } from '@/common/utils/useUploadImgs' // 引入公共上传逻辑 |
| 158 | +import { getRoadListByLatLng } from '@/api/common' | ||
| 159 | +import { createQuick } from '@/api/quick-order/quick-order' | ||
| 85 | 160 | ||
| 86 | -<script lang="ts"> | ||
| 87 | -import { uploadImages } from '@/common/utils/upload' | ||
| 88 | -import { maintainCreate } from "@/api/maintain-manage/maintain-manage" | ||
| 89 | - | ||
| 90 | -export default { | ||
| 91 | - data() { | ||
| 92 | - return { | ||
| 93 | - imagesList: [], | ||
| 94 | - initProgress: 0, | ||
| 95 | - inspectForm: { | ||
| 96 | - remark: '', | ||
| 97 | - progress: 0 | ||
| 98 | - }, | ||
| 99 | - paramsOptins: {}, | ||
| 100 | - inspectFormRules: { | ||
| 101 | - images: [ | ||
| 102 | - { | ||
| 103 | - required: true, | ||
| 104 | - message: '请上传图片', | ||
| 105 | - trigger: 'change', | ||
| 106 | - validator: (rule, value, callback) => { | ||
| 107 | - const hasSuccessImg = this.imagesList.some(item => item.status === 'success') | ||
| 108 | - const imgCount = this.imagesList.filter(item => item.status === 'success').length | ||
| 109 | - if (!hasSuccessImg || imgCount < 1) { | ||
| 110 | - callback(new Error('最少需要上传1张图片')) | ||
| 111 | - } else if (imgCount > 3) { | ||
| 112 | - callback(new Error('最多只能上传3张图片')) | ||
| 113 | - } else { | ||
| 114 | - callback() | ||
| 115 | - } | ||
| 116 | - } | ||
| 117 | - } | ||
| 118 | - ], | ||
| 119 | - // progress: [ | ||
| 120 | - // { | ||
| 121 | - // | ||
| 122 | - // required: true, | ||
| 123 | - // message: '请设置完成进度', | ||
| 124 | - // trigger: ['change'], | ||
| 125 | - // validator: (rule, value, callback) => { | ||
| 126 | - // // 第一步:校验是否为空/0 | ||
| 127 | - // if (!value && value !== 0) { | ||
| 128 | - // callback(new Error('请设置完成进度')) | ||
| 129 | - // } | ||
| 130 | - // // 第二步:校验是否大于初始进度 | ||
| 131 | - // else if (value <= this.initProgress) { | ||
| 132 | - // callback(new Error(`完成进度必须大于${this.initProgress}%`)) | ||
| 133 | - // } | ||
| 134 | - // // 校验通过 | ||
| 135 | - // else { | ||
| 136 | - // callback() | ||
| 137 | - // } | ||
| 138 | - // } | ||
| 139 | - // } | ||
| 140 | - // ] | ||
| 141 | - } | ||
| 142 | - } | ||
| 143 | - }, | ||
| 144 | - onLoad(option) { | ||
| 145 | - console.log('页面参数:', option) | ||
| 146 | - this.paramsOptins = option | ||
| 147 | - // 初始化初始进度 | ||
| 148 | - this.initProgress = option.finishPercent ? Number(option.finishPercent)+1 : 0 | ||
| 149 | - // 关键修复:初始进度值设为 初始进度+1,避免刚进入就触发校验失败 | ||
| 150 | - this.inspectForm.progress = this.initProgress | ||
| 151 | - console.log('初始进度值:', this.initProgress) | ||
| 152 | - }, | ||
| 153 | - onReady() { | ||
| 154 | - this.$refs.inspectFormRef.setRules(this.inspectFormRules) | ||
| 155 | - console.log('巡查表单规则初始化完成') | ||
| 156 | - }, | ||
| 157 | - methods: { | ||
| 158 | - /** | ||
| 159 | - * 进度滑块变化处理 - 核心优化:实时触发校验 + 状态更新 | ||
| 160 | - */ | ||
| 161 | - handleProgressChange(value) { | ||
| 162 | - // // 1. 强制修正非法值(兜底) | ||
| 163 | - // console.log(value) | ||
| 164 | - // if (value <= this.initProgress) { | ||
| 165 | - // console.log('123') | ||
| 166 | - // this.inspectForm.progress = this.initProgress + 1 | ||
| 167 | - // uni.showToast({ | ||
| 168 | - // title: `进度不能低于${this.initProgress}%`, | ||
| 169 | - // icon: 'none', | ||
| 170 | - // duration: 1500 | ||
| 171 | - // }) | ||
| 172 | - // } | ||
| 173 | - // | ||
| 174 | - // // 2. 关键:手动触发progress字段的校验,实时更新提示状态 | ||
| 175 | - // this.$nextTick(async () => { | ||
| 176 | - // try { | ||
| 177 | - // // 触发单个字段校验 | ||
| 178 | - // await this.$refs.inspectFormRef.validateField('progress') | ||
| 179 | - // } catch (err) { | ||
| 180 | - // // 校验失败时uView会自动显示提示,此处无需额外处理 | ||
| 181 | - // console.log('进度校验失败:', err) | ||
| 182 | - // } | ||
| 183 | - // }) | ||
| 184 | - }, | 161 | +// 表单Ref |
| 162 | +const workOrderFormRef = ref<UniFormRef>(null) | ||
| 185 | 163 | ||
| 186 | - /** | ||
| 187 | - * 删除图片 | ||
| 188 | - */ | ||
| 189 | - deleteImg(event) { | ||
| 190 | - console.log('删除图片事件:', event) | ||
| 191 | - this.imagesList.splice(event.index, 1) | ||
| 192 | - this.$refs.inspectFormRef.validateField('images') | ||
| 193 | - uni.showToast({ title: '图片删除成功', icon: 'success' }) | ||
| 194 | - }, | 164 | +// ========== 复用公共上传逻辑 ========== |
| 165 | +// 1. 问题照片配置 | ||
| 166 | +const problemImgs = useUploadImgs({ | ||
| 167 | + maxCount: 3, // 可自定义数量 | ||
| 168 | + uploadText: '选择问题照片', | ||
| 169 | + sizeType: ['compressed'], | ||
| 170 | + formRef: workOrderFormRef, | ||
| 171 | + fieldName: 'problemImgs' | ||
| 172 | +}) | ||
| 173 | + | ||
| 174 | +// 2. 完成照片配置 | ||
| 175 | +const completeImgs = useUploadImgs({ | ||
| 176 | + maxCount: 3, | ||
| 177 | + uploadText: '选择完成照片', | ||
| 178 | + sizeType: ['compressed'], | ||
| 179 | + formRef: workOrderFormRef, | ||
| 180 | + fieldName: 'completeImgs' | ||
| 181 | +}) | ||
| 182 | + | ||
| 183 | +// ========== 页面数据 ========== | ||
| 184 | +const showRoadName = ref(false) | ||
| 185 | +const showOrderName = ref(false) | ||
| 186 | +const roadNameList = ref<any[]>([]) | ||
| 187 | +const orderNameList = ref<any[]>([]) | ||
| 188 | + | ||
| 189 | +// 工单表单数据 | ||
| 190 | +const workOrderForm = reactive({ | ||
| 191 | + roadId: 0, | ||
| 192 | + roadName: '', | ||
| 193 | + workLocation: '', | ||
| 194 | + orderName: '', | ||
| 195 | + problemDesc: '', | ||
| 196 | + handleResult: '', | ||
| 197 | + lat: 0, | ||
| 198 | + lon: 0 | ||
| 199 | +}) | ||
| 200 | + | ||
| 201 | +// 表单校验规则 | ||
| 202 | +const workOrderFormRules = reactive({ | ||
| 203 | + workLocation: [ | ||
| 204 | + { type: 'string', required: true, message: '请选择工单位置', trigger: ['change', 'blur'] } | ||
| 205 | + ], | ||
| 206 | + roadName: [ | ||
| 207 | + { type: 'string', required: true, message: '请选择道路名称', trigger: ['change', 'blur'] } | ||
| 208 | + ], | ||
| 209 | + orderName: [ | ||
| 210 | + { type: 'string', required: true, message: '请选择工单名称', trigger: ['change', 'blur'] } | ||
| 211 | + ], | ||
| 212 | + problemDesc: [ | ||
| 213 | + { type: 'string', required: true, message: '请输入情况描述', trigger: ['change', 'blur'] }, | ||
| 214 | + { type: 'string', min: 3, max: 200, message: '情况描述需3-200字', trigger: ['change', 'blur'] } | ||
| 215 | + ], | ||
| 216 | + problemImgs: [problemImgs.imgValidateRule], // 复用校验规则 | ||
| 217 | + completeImgs: [completeImgs.imgValidateRule] // 复用校验规则 | ||
| 218 | +}) | ||
| 219 | + | ||
| 220 | +// ========== 生命周期 ========== | ||
| 221 | +onReady(() => { | ||
| 222 | + // 设置表单校验规则 | ||
| 223 | + workOrderFormRef.value?.setRules(workOrderFormRules) | ||
| 224 | + console.log('工单表单规则初始化完成') | ||
| 225 | +}) | ||
| 226 | + | ||
| 227 | +onShow(() => { | ||
| 228 | + console.log(uni.$dict.getDictLabel('ai_image_status', 20)) | ||
| 229 | + console.log(uni.$dict.getDictSimpleList('work_name')) | ||
| 230 | + orderNameList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('work_name')) | ||
| 231 | + console.log(orderNameList.value) | ||
| 232 | +}) | ||
| 233 | + | ||
| 234 | +// ========== 方法 ========== | ||
| 235 | +/** | ||
| 236 | + * 返回上一页 | ||
| 237 | + */ | ||
| 238 | +const navigateBack = () => { | ||
| 239 | + uni.navigateBack() | ||
| 240 | +} | ||
| 241 | + | ||
| 242 | +/** | ||
| 243 | + * 隐藏键盘 | ||
| 244 | + */ | ||
| 245 | +const hideKeyboard = () => { | ||
| 246 | + uni.hideKeyboard() | ||
| 247 | +} | ||
| 248 | + | ||
| 249 | +/** | ||
| 250 | + * 选择工单位置 | ||
| 251 | + */ | ||
| 252 | +const chooseWorkLocation = async () => { | ||
| 253 | + uni.chooseLocation({ | ||
| 254 | + success: async (res) => { | ||
| 255 | + workOrderForm.roadName = '' | ||
| 256 | + workOrderForm.roadId = 0 | ||
| 257 | + roadNameList.value = [] | ||
| 258 | + | ||
| 259 | + workOrderForm.workLocation = res.name | ||
| 260 | + workOrderForm.lat = res.latitude | ||
| 261 | + workOrderForm.lon = res.longitude | ||
| 195 | 262 | ||
| 196 | - /** | ||
| 197 | - * 上传图片 | ||
| 198 | - */ | ||
| 199 | - async uploadImgs(event) { | ||
| 200 | - console.log('上传图片事件:', event) | ||
| 201 | - const fileList = Array.isArray(event.file) ? event.file : [event.file] | ||
| 202 | - const targetImgList = this.imagesList | ||
| 203 | - const filePaths = fileList.map(item => item.url) | ||
| 204 | - const tempItems = fileList.map(item => ({ | ||
| 205 | - ...item, | ||
| 206 | - status: 'uploading', | ||
| 207 | - message: '上传中' | ||
| 208 | - })) | ||
| 209 | - const startIndex = targetImgList.length | ||
| 210 | - targetImgList.push(...tempItems) | 263 | + workOrderFormRef.value?.validateField('workLocation') |
| 264 | + workOrderFormRef.value?.validateField('roadName') | ||
| 211 | 265 | ||
| 212 | try { | 266 | try { |
| 213 | - const uploadResultUrls = await uploadImages({ | ||
| 214 | - filePaths: filePaths, | ||
| 215 | - ignoreError: true | ||
| 216 | - }) | ||
| 217 | - console.log('上传成功的URL列表:', uploadResultUrls) | ||
| 218 | - | ||
| 219 | - uploadResultUrls.forEach((url, index) => { | ||
| 220 | - if (targetImgList[startIndex + index]) { | ||
| 221 | - targetImgList.splice(startIndex + index, 1, { | ||
| 222 | - ...fileList[index], | ||
| 223 | - status: 'success', | ||
| 224 | - message: '', | ||
| 225 | - url: url | ||
| 226 | - }) | ||
| 227 | - } | 267 | + uni.showLoading({ title: '获取道路名称中...' }) |
| 268 | + const roadRes = await getRoadListByLatLng({ | ||
| 269 | + companyCode: 'sls', | ||
| 270 | + latitude: res.latitude, | ||
| 271 | + longitude: res.longitude | ||
| 228 | }) | 272 | }) |
| 273 | + uni.hideLoading() | ||
| 229 | 274 | ||
| 230 | - if (uploadResultUrls.length < fileList.length) { | ||
| 231 | - const failCount = fileList.length - uploadResultUrls.length | ||
| 232 | - for (let i = uploadResultUrls.length; i < fileList.length; i++) { | ||
| 233 | - if (targetImgList[startIndex + i]) { | ||
| 234 | - targetImgList.splice(startIndex + i, 1, { | ||
| 235 | - ...fileList[i], | ||
| 236 | - status: 'failed', | ||
| 237 | - message: '上传失败' | ||
| 238 | - }) | ||
| 239 | - } | ||
| 240 | - } | ||
| 241 | - uni.showToast({ title: `成功上传${uploadResultUrls.length}张,失败${failCount}张`, icon: 'none' }) | 275 | + if (Array.isArray(roadRes)) { |
| 276 | + roadNameList.value = roadRes.map((item) => ({ | ||
| 277 | + name: item.roadName || '', | ||
| 278 | + code: item.roadCode || '', | ||
| 279 | + id: item.roadId || 0 | ||
| 280 | + })) | ||
| 242 | } else { | 281 | } else { |
| 243 | - uni.showToast({ title: `成功上传${fileList.length}张图片`, icon: 'success' }) | 282 | + roadNameList.value = [{ name: '未查询到道路名称', code: '', id: 0 }] |
| 283 | + uni.showToast({ title: '未查询到该位置的道路信息', icon: 'none' }) | ||
| 244 | } | 284 | } |
| 245 | - | ||
| 246 | - this.$refs.inspectFormRef.validateField('images') | ||
| 247 | } catch (err) { | 285 | } catch (err) { |
| 248 | - console.error('图片上传失败:', err) | ||
| 249 | - for (let i = 0; i < fileList.length; i++) { | ||
| 250 | - if (targetImgList[startIndex + i]) { | ||
| 251 | - targetImgList.splice(startIndex + i, 1, { | ||
| 252 | - ...fileList[i], | ||
| 253 | - status: 'failed', | ||
| 254 | - message: '上传失败' | ||
| 255 | - }) | ||
| 256 | - } | ||
| 257 | - } | ||
| 258 | - uni.showToast({ title: '图片上传失败,请重试', icon: 'none' }) | ||
| 259 | - this.$refs.inspectFormRef.validateField('images') | 286 | + uni.hideLoading() |
| 287 | + console.error('获取道路名称失败:', err) | ||
| 288 | + uni.showToast({ title: '获取道路名称失败,请重试', icon: 'none' }) | ||
| 289 | + roadNameList.value = [{ name: '获取失败,请重新选择位置', code: '', id: 0 }] | ||
| 260 | } | 290 | } |
| 261 | }, | 291 | }, |
| 292 | + fail: (err) => { | ||
| 293 | + console.error('选择位置失败:', err) | ||
| 294 | + uni.showToast({ title: '选择位置失败:' + err.errMsg, icon: 'none' }) | ||
| 295 | + } | ||
| 296 | + }) | ||
| 297 | +} | ||
| 262 | 298 | ||
| 263 | - /** | ||
| 264 | - * 处理图片超出数量限制 | ||
| 265 | - */ | ||
| 266 | - handleExceed() { | ||
| 267 | - uni.showToast({ title: '最多只能上传3张图片', icon: 'none' }) | ||
| 268 | - }, | 299 | +/** |
| 300 | + * 选择道路名称 | ||
| 301 | + */ | ||
| 302 | +const handleRoadNameSelect = (e: any) => { | ||
| 303 | + console.log('选择道路名称:', e) | ||
| 304 | + workOrderForm.roadName = e.name | ||
| 305 | + workOrderForm.roadId = e.code | ||
| 306 | + showRoadName.value = false | ||
| 307 | + workOrderFormRef.value?.validateField('roadName') | ||
| 308 | +} | ||
| 269 | 309 | ||
| 270 | - /** | ||
| 271 | - * 提取图片URL数组 | ||
| 272 | - */ | ||
| 273 | - getImgUrlList(imgList) { | ||
| 274 | - return imgList.filter(item => item.status === 'success').map(item => item.url) | ||
| 275 | - }, | 310 | +/** |
| 311 | + * 选择工单名称 | ||
| 312 | + */ | ||
| 313 | +const handleOrderNameSelect = (e: any) => { | ||
| 314 | + console.log(e) | ||
| 315 | + workOrderForm.orderName = e.name | ||
| 316 | + showOrderName.value = false | ||
| 317 | + workOrderFormRef.value?.validateField('orderName') | ||
| 318 | +} | ||
| 276 | 319 | ||
| 277 | - /** | ||
| 278 | - * 提交巡查表单 | ||
| 279 | - */ | ||
| 280 | - async submitInspect() { | ||
| 281 | - console.log('当前完成进度:', this.inspectForm.progress) | ||
| 282 | - try { | ||
| 283 | - await this.$refs.inspectFormRef.validate() | ||
| 284 | - console.log('图片列表:', this.imagesList) | ||
| 285 | - | ||
| 286 | - const submitData = { | ||
| 287 | - totalFinishPercent: this.inspectForm.progress, | ||
| 288 | - "planNo": this.paramsOptins.planNo, | ||
| 289 | - "imgHost": "1", | ||
| 290 | - "beginImg": this.getImgUrlList(this.imagesList), | ||
| 291 | - "commonUserList": [], | ||
| 292 | - "remark": this.inspectForm.remark | ||
| 293 | - } | 320 | +/** |
| 321 | + * 提交工单 | ||
| 322 | + */ | ||
| 323 | +const submitWorkOrder = async () => { | ||
| 324 | + try { | ||
| 325 | + // 表单校验 | ||
| 326 | + await workOrderFormRef.value?.validate() | ||
| 294 | 327 | ||
| 295 | - uni.showLoading({ title: '提交中...' }) | ||
| 296 | - await maintainCreate(submitData) | ||
| 297 | - uni.hideLoading() | 328 | + const submitData = { |
| 329 | + roadId: workOrderForm.roadId, | ||
| 330 | + roadName: workOrderForm.roadName, | ||
| 331 | + imgs: problemImgs.getSuccessImgUrls(), // 复用获取成功URL方法 | ||
| 332 | + longRangeImgList: completeImgs.getSuccessImgUrls(), // 复用获取成功URL方法 | ||
| 333 | + remark: workOrderForm.problemDesc, | ||
| 334 | + handleResult: workOrderForm.handleResult, | ||
| 335 | + latLonType: 2, | ||
| 336 | + lat: workOrderForm.lat, | ||
| 337 | + lon: workOrderForm.lon, | ||
| 338 | + lonLatAddress: workOrderForm.workLocation, | ||
| 339 | + pressingType: 2, | ||
| 340 | + orderName: workOrderForm.orderName, | ||
| 341 | + sourceId: 1, | ||
| 342 | + sourceName: '园林', | ||
| 343 | + thirdWorkNo: '' | ||
| 344 | + } | ||
| 298 | 345 | ||
| 299 | - uni.showToast({ | ||
| 300 | - title: '提交成功', | ||
| 301 | - icon: 'success', | ||
| 302 | - duration: 1000 | ||
| 303 | - }) | 346 | + // 显示加载中 |
| 347 | + uni.showLoading({ title: '提交中...' }) | ||
| 304 | 348 | ||
| 305 | - setTimeout(() => { | ||
| 306 | - uni.redirectTo({ | ||
| 307 | - url: '/pages-sub/daily/maintain-manage/index' | ||
| 308 | - }) | ||
| 309 | - }, 1000) | 349 | + // 调用提交接口 |
| 350 | + const res = await createQuick(submitData) | ||
| 310 | 351 | ||
| 311 | - } catch (error) { | ||
| 312 | - uni.hideLoading() | ||
| 313 | - if (!Array.isArray(error)) { | ||
| 314 | - console.error('巡查表单提交失败:', error) | ||
| 315 | - uni.showToast({ | ||
| 316 | - title: '提交失败,请重试', | ||
| 317 | - icon: 'none', | ||
| 318 | - duration: 2000 | ||
| 319 | - }) | ||
| 320 | - } | ||
| 321 | - } | 352 | + uni.hideLoading() |
| 353 | + uni.showToast({ | ||
| 354 | + title: '工单提交成功', | ||
| 355 | + icon: 'success', | ||
| 356 | + duration: 1000 | ||
| 357 | + }) | ||
| 358 | + | ||
| 359 | + // 延迟跳转 | ||
| 360 | + setTimeout(() => { | ||
| 361 | + uni.redirectTo({ | ||
| 362 | + url: '/pages-sub/daily/quick-order/index' | ||
| 363 | + }) | ||
| 364 | + }, 1000) | ||
| 365 | + } catch (error) { | ||
| 366 | + uni.hideLoading() | ||
| 367 | + | ||
| 368 | + if (!Array.isArray(error)) { | ||
| 369 | + console.error('工单提交失败:', error) | ||
| 370 | + uni.showToast({ | ||
| 371 | + title: '提交失败,请重试', | ||
| 372 | + icon: 'none', | ||
| 373 | + duration: 2000 | ||
| 374 | + }) | ||
| 322 | } | 375 | } |
| 323 | } | 376 | } |
| 324 | } | 377 | } |
| 325 | </script> | 378 | </script> |
| 326 | 379 | ||
| 327 | <style lang="scss" scoped> | 380 | <style lang="scss" scoped> |
| 328 | -.inspect-form-content { | 381 | +// 全局页面样式 |
| 382 | +.u-page { | ||
| 383 | + min-height: 100vh; | ||
| 384 | +} | ||
| 385 | + | ||
| 386 | +// 工单表单内容容器 | ||
| 387 | +.work-order-form-content { | ||
| 329 | background: #fff; | 388 | background: #fff; |
| 330 | - padding: 20rpx; | ||
| 331 | - margin-bottom: 20rpx; | ||
| 332 | } | 389 | } |
| 333 | 390 | ||
| 334 | -.progress-wrap { | ||
| 335 | - width: 83%; | 391 | +.fixed-bottom-btn-wrap { |
| 392 | + padding: 20rpx; | ||
| 393 | + background: #fff; | ||
| 336 | } | 394 | } |
| 337 | </style> | 395 | </style> |
| 338 | \ No newline at end of file | 396 | \ No newline at end of file |
pages-sub/daily/quick-order/add-order.vue
| @@ -151,18 +151,11 @@ | @@ -151,18 +151,11 @@ | ||
| 151 | </template> | 151 | </template> |
| 152 | 152 | ||
| 153 | <script setup lang="ts"> | 153 | <script setup lang="ts"> |
| 154 | -import {ref, inject} from 'vue' | 154 | +import {ref} from 'vue' |
| 155 | 155 | ||
| 156 | import type {UniFormRef} from '@/uni_modules/uview-plus/types' | 156 | import type {UniFormRef} from '@/uni_modules/uview-plus/types' |
| 157 | // 定义ref供选项式API使用 | 157 | // 定义ref供选项式API使用 |
| 158 | const workOrderFormRef = ref<UniFormRef>(null) | 158 | const workOrderFormRef = ref<UniFormRef>(null) |
| 159 | -// | ||
| 160 | -// // 注入全局字典工具 | ||
| 161 | -// const $dict = inject('$dict'); | ||
| 162 | -// const dictLabel = ref(''); | ||
| 163 | -// const modeList = ref([]); | ||
| 164 | -// | ||
| 165 | - | ||
| 166 | 159 | ||
| 167 | </script> | 160 | </script> |
| 168 | 161 | ||
| @@ -170,11 +163,7 @@ const workOrderFormRef = ref<UniFormRef>(null) | @@ -170,11 +163,7 @@ const workOrderFormRef = ref<UniFormRef>(null) | ||
| 170 | import {getRoadListByLatLng} from '@/api/common' | 163 | import {getRoadListByLatLng} from '@/api/common' |
| 171 | import {uploadImages} from '@/common/utils/upload'; | 164 | import {uploadImages} from '@/common/utils/upload'; |
| 172 | import {createQuick} from '@/api/quick-order/quick-order' | 165 | import {createQuick} from '@/api/quick-order/quick-order' |
| 173 | -// import { getCurrentInstance } from 'vue'; | ||
| 174 | -// const { proxy } = getCurrentInstance(); | ||
| 175 | -// const label = $dict.getDictLabel('ai_image_status', 10); | ||
| 176 | -// const opdata = $dict.getDictLabel('ai_image_status'); | ||
| 177 | -// console.log(opdata) | 166 | + |
| 178 | 167 | ||
| 179 | export default { | 168 | export default { |
| 180 | data() { | 169 | data() { |
| @@ -468,7 +457,8 @@ export default { | @@ -468,7 +457,8 @@ export default { | ||
| 468 | orderName: this.workOrderForm.orderName, | 457 | orderName: this.workOrderForm.orderName, |
| 469 | sourceId: 1, | 458 | sourceId: 1, |
| 470 | sourceName: '园林', | 459 | sourceName: '园林', |
| 471 | - thirdWorkNo: '' | 460 | + thirdWorkNo: '', |
| 461 | + busiLine:'yl' | ||
| 472 | } | 462 | } |
| 473 | 463 | ||
| 474 | // 显示加载中 | 464 | // 显示加载中 |
pages-sub/problem/work-order-manage/index.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <view class="page-container"> | ||
| 3 | + <!-- 顶部固定区域 --> | ||
| 4 | + <up-sticky> | ||
| 5 | + <view class="header-wrap"> | ||
| 6 | + <!-- 第一行:u-tabs 待办/已办切换 :scrollable="false"--> | ||
| 7 | + <up-tabs | ||
| 8 | + v-model="activeTab" | ||
| 9 | + :list="tabList" | ||
| 10 | + active-color="#1989fa" | ||
| 11 | + inactive-color="#666" | ||
| 12 | + font-size="30rpx" | ||
| 13 | + | ||
| 14 | + @change="handleTabChange" | ||
| 15 | + /> | ||
| 16 | + | ||
| 17 | + <!-- 第二行:下拉框 + 搜索框 --> | ||
| 18 | + <view class="search-header"> | ||
| 19 | + <!-- 左侧下拉框 --> | ||
| 20 | + <view class="select-wrap"> | ||
| 21 | + <up-select | ||
| 22 | + v-model:current="selectedSortValue" | ||
| 23 | + :options="sortOptions" | ||
| 24 | + :showOptionsLabel="true" | ||
| 25 | + @select="handleSortChange" | ||
| 26 | + border="surround" | ||
| 27 | + :style="{ flex: 1 }" | ||
| 28 | + /> | ||
| 29 | + </view> | ||
| 30 | + | ||
| 31 | + <!-- 右侧搜索框 --> | ||
| 32 | + <view class="search-input-wrap"> | ||
| 33 | + <up-search | ||
| 34 | + v-model="searchValue" | ||
| 35 | + placeholder="请输入关键字" | ||
| 36 | + @search="handleSearch" | ||
| 37 | + bg-color="#f5f5f5" | ||
| 38 | + :clearabled="false" | ||
| 39 | + :show-action="true" | ||
| 40 | + actionText="搜索" | ||
| 41 | + :animation="true" | ||
| 42 | + @custom="handleSearch" | ||
| 43 | + /> | ||
| 44 | + </view> | ||
| 45 | + </view> | ||
| 46 | + </view> | ||
| 47 | + </up-sticky> | ||
| 48 | + | ||
| 49 | + <!-- 列表容器 --> | ||
| 50 | + <z-paging | ||
| 51 | + ref="paging" | ||
| 52 | + v-model="orderList" | ||
| 53 | + @query="queryList" | ||
| 54 | + :auto-show-system-loading="true" | ||
| 55 | + | ||
| 56 | + > | ||
| 57 | + <template #empty> | ||
| 58 | + <view class="empty-view" style="padding: 100rpx 0; text-align: center; color: #999;"> | ||
| 59 | + 暂无工单数据 | ||
| 60 | + </view> | ||
| 61 | + </template> | ||
| 62 | + | ||
| 63 | + <view class="common-card-list" slot="top"> | ||
| 64 | + <!-- 待办工单卡片 --> | ||
| 65 | + <up-card | ||
| 66 | + v-if="activeTab === 0" | ||
| 67 | + :border="false" | ||
| 68 | + :foot-border-top="false" | ||
| 69 | + v-for="(item, index) in orderList" | ||
| 70 | + :key="`todo_${item.orderNo}_${index}`" | ||
| 71 | + :show-head="false" | ||
| 72 | + class="order-card" | ||
| 73 | + > | ||
| 74 | + <template #body> | ||
| 75 | + <view class="card-body"> | ||
| 76 | + <view class="u-body-item u-flex"> | ||
| 77 | + <view class="u-body-item-title">工单编号:</view> | ||
| 78 | + <view class="u-line-1 u-body-value">{{ item.orderNo || '-' }}</view> | ||
| 79 | + </view> | ||
| 80 | + <view class="u-body-item u-flex"> | ||
| 81 | + <view class="u-body-item-title">工单位置:</view> | ||
| 82 | + <view class="u-line-1 u-body-value">{{ item.roadName || '-' }}</view> | ||
| 83 | + </view> | ||
| 84 | + <view class="u-body-item u-flex"> | ||
| 85 | + <view class="u-body-item-title">工单名称:</view> | ||
| 86 | + <view class="u-line-1 u-body-value">{{ item.orderName || '未填写' }}</view> | ||
| 87 | + </view> | ||
| 88 | + <view class="u-body-item u-flex"> | ||
| 89 | + <view class="u-body-item-title">情况描述:</view> | ||
| 90 | + <view class="u-line-1 u-body-value">{{ item.remark || '无' }}</view> | ||
| 91 | + </view> | ||
| 92 | + <view class="u-body-item u-flex"> | ||
| 93 | + <view class="u-body-item-title">紧急程度:</view> | ||
| 94 | + <view class="u-line-1 u-body-value"> | ||
| 95 | + <up-tag :type="getUrgencyType(item.urgencyLevel)">{{ item.urgencyLevel || '普通' }}</up-tag> | ||
| 96 | + </view> | ||
| 97 | + </view> | ||
| 98 | + <view class="u-body-item u-flex"> | ||
| 99 | + <view class="u-body-item-title">提交时间:</view> | ||
| 100 | + <view class="u-line-1 u-body-value">{{ timeFormat(item.createTime, 'yyyy-mm-dd hh:MM:ss') }}</view> | ||
| 101 | + </view> | ||
| 102 | + <!-- 操作按钮行 --> | ||
| 103 | + <view class="u-body-item u-flex common-justify-between common-item-center mt-20"> | ||
| 104 | + <up-button type="warning" size="mini" @click="handleReject(item)">回退</up-button> | ||
| 105 | + <up-button type="primary" size="mini" @click="handleProcess(item)">处理</up-button> | ||
| 106 | + <up-button type="info" size="mini" @click="handleDetail(item)">详情</up-button> | ||
| 107 | + </view> | ||
| 108 | + </view> | ||
| 109 | + </template> | ||
| 110 | + </up-card> | ||
| 111 | + | ||
| 112 | + <!-- 已办工单卡片 --> | ||
| 113 | + <up-card | ||
| 114 | + v-if="activeTab === 1" | ||
| 115 | + :border="false" | ||
| 116 | + :foot-border-top="false" | ||
| 117 | + v-for="(item, index) in orderList" | ||
| 118 | + :key="`done_${item.orderNo}_${index}`" | ||
| 119 | + :show-head="false" | ||
| 120 | + class="order-card" | ||
| 121 | + > | ||
| 122 | + <template #body> | ||
| 123 | + <view class="card-body"> | ||
| 124 | + <view class="u-body-item u-flex"> | ||
| 125 | + <view class="u-body-item-title">工单编号:</view> | ||
| 126 | + <view class="u-line-1 u-body-value">{{ item.orderNo || '-' }}</view> | ||
| 127 | + </view> | ||
| 128 | + <view class="u-body-item u-flex"> | ||
| 129 | + <view class="u-body-item-title">工单位置:</view> | ||
| 130 | + <view class="u-line-1 u-body-value">{{ item.roadName || '-' }}</view> | ||
| 131 | + </view> | ||
| 132 | + <view class="u-body-item u-flex"> | ||
| 133 | + <view class="u-body-item-title">工单名称:</view> | ||
| 134 | + <view class="u-line-1 u-body-value">{{ item.orderName || '未填写' }}</view> | ||
| 135 | + </view> | ||
| 136 | + <view class="u-body-item u-flex"> | ||
| 137 | + <view class="u-body-item-title">情况描述:</view> | ||
| 138 | + <view class="u-line-1 u-body-value">{{ item.remark || '无' }}</view> | ||
| 139 | + </view> | ||
| 140 | + <view class="u-body-item u-flex common-justify-between common-item-center"> | ||
| 141 | + <view class="u-flex"> | ||
| 142 | + <view class="u-body-item-title">紧急程度:</view> | ||
| 143 | + <view class="u-line-1 u-body-value"> | ||
| 144 | + <up-tag :type="getUrgencyType(item.urgencyLevel)">{{ item.urgencyLevel || '普通' }}</up-tag> | ||
| 145 | + </view> | ||
| 146 | + </view> | ||
| 147 | + <up-button type="info" size="mini" @click="handleDetail(item)">工单详情</up-button> | ||
| 148 | + </view> | ||
| 149 | + <view class="u-body-item u-flex"> | ||
| 150 | + <view class="u-body-item-title">提交时间:</view> | ||
| 151 | + <view class="u-line-1 u-body-value">{{ timeFormat(item.createTime, 'yyyy-mm-dd hh:MM:ss') }}</view> | ||
| 152 | + </view> | ||
| 153 | + </view> | ||
| 154 | + </template> | ||
| 155 | + </up-card> | ||
| 156 | + </view> | ||
| 157 | + </z-paging> | ||
| 158 | + | ||
| 159 | + <!-- 底部新增工单按钮(仅巡查员显示) --> | ||
| 160 | + <view v-if="isInspector" class="fixed-bottom-btn-wrap"> | ||
| 161 | + <up-button type="primary" size="large" @click="handleAddOrder"> | ||
| 162 | + 新增工单 | ||
| 163 | + </up-button> | ||
| 164 | + </view> | ||
| 165 | + | ||
| 166 | + <!-- 回退原因弹窗 --> | ||
| 167 | + <up-popup v-model="rejectPopupShow" mode="center" :close-on-click-overlay="false"> | ||
| 168 | + <view class="reject-popup"> | ||
| 169 | + <view class="popup-title">回退原因</view> | ||
| 170 | + <up-textarea | ||
| 171 | + v-model="rejectReason" | ||
| 172 | + placeholder="请输入回退原因(必填)" | ||
| 173 | + :required="true" | ||
| 174 | + maxlength="-1" | ||
| 175 | + rows="4" | ||
| 176 | + class="mt-20" | ||
| 177 | + /> | ||
| 178 | + <view class="upload-wrap mt-20"> | ||
| 179 | + <view class="upload-title">上传图片(选填)</view> | ||
| 180 | + <up-upload | ||
| 181 | + :action="uploadUrl" | ||
| 182 | + :file-list="rejectFileList" | ||
| 183 | + @after-read="handleAfterRead" | ||
| 184 | + @delete="handleDeleteFile" | ||
| 185 | + multiple | ||
| 186 | + max-count="3" | ||
| 187 | + /> | ||
| 188 | + </view> | ||
| 189 | + <view class="popup-btn-wrap mt-40"> | ||
| 190 | + <up-button type="default" size="medium" @click="rejectPopupShow = false" class="mr-20">取消</up-button> | ||
| 191 | + <up-button type="primary" size="medium" @click="confirmReject">确认提交</up-button> | ||
| 192 | + </view> | ||
| 193 | + </view> | ||
| 194 | + </up-popup> | ||
| 195 | + </view> | ||
| 196 | +</template> | ||
| 197 | + | ||
| 198 | +<script setup> | ||
| 199 | +import { ref, computed, onMounted } from 'vue'; | ||
| 200 | +import { timeFormat } from '@/uni_modules/uview-plus'; | ||
| 201 | +// 假设从用户store获取角色信息 | ||
| 202 | +import { useUserStore } from '@/pinia/user'; | ||
| 203 | + | ||
| 204 | +// ========== 状态管理 ========== | ||
| 205 | +const userStore = useUserStore(); | ||
| 206 | +// 标签页切换 | ||
| 207 | +const activeTab = ref(0); // 0-待办 1-已办 | ||
| 208 | +const tabList = ref([ | ||
| 209 | + { name: '待办' }, | ||
| 210 | + { name: '已办' } | ||
| 211 | +]); | ||
| 212 | +// 排序下拉框 | ||
| 213 | +const selectedSortValue = ref(1); | ||
| 214 | +const sortOptions = ref([ | ||
| 215 | + { name: '位置', id: 1 }, | ||
| 216 | + { name: '名称', id: 2 }, | ||
| 217 | + { name: '描述', id: 3 }, | ||
| 218 | + { name: '编号', id: 4 }, | ||
| 219 | +]); | ||
| 220 | +// 搜索 | ||
| 221 | +const searchValue = ref(''); | ||
| 222 | +// 分页 | ||
| 223 | +const paging = ref(null); | ||
| 224 | +const orderList = ref([]); | ||
| 225 | +// 角色控制(巡查员显示新增按钮) | ||
| 226 | +const isInspector = computed(() => { | ||
| 227 | + // 假设用户角色字段为role,巡查员标识为inspector | ||
| 228 | + return userStore.userInfo.roles === 'yl_inspector'; | ||
| 229 | +}); | ||
| 230 | +// 回退弹窗相关 | ||
| 231 | +const rejectPopupShow = ref(false); | ||
| 232 | +const rejectReason = ref(''); | ||
| 233 | +const rejectFileList = ref([]); | ||
| 234 | +const currentRejectItem = ref(null); | ||
| 235 | +// 上传地址(根据实际接口配置) | ||
| 236 | +const uploadUrl = ref('https://xxx.com/upload'); | ||
| 237 | + | ||
| 238 | +// ========== 接口请求 ========== | ||
| 239 | +// 待办工单接口 | ||
| 240 | +const getTodoOrderList = async (params) => { | ||
| 241 | + // 替换为实际待办工单接口 | ||
| 242 | + const res = await uni.request({ | ||
| 243 | + url: '/api/order/todo', | ||
| 244 | + method: 'POST', | ||
| 245 | + data: params | ||
| 246 | + }); | ||
| 247 | + return res.data; | ||
| 248 | +}; | ||
| 249 | + | ||
| 250 | +// 已办工单接口 | ||
| 251 | +const getDoneOrderList = async (params) => { | ||
| 252 | + // 替换为实际已办工单接口 | ||
| 253 | + const res = await uni.request({ | ||
| 254 | + url: '/api/order/done', | ||
| 255 | + method: 'POST', | ||
| 256 | + data: params | ||
| 257 | + }); | ||
| 258 | + return res.data; | ||
| 259 | +}; | ||
| 260 | + | ||
| 261 | +// 分页查询列表 | ||
| 262 | +const queryList = async (pageNo, pageSize) => { | ||
| 263 | + try { | ||
| 264 | + const apiParams = { | ||
| 265 | + searchContent: searchValue.value.trim() || '', | ||
| 266 | + pageNo, | ||
| 267 | + pageSize, | ||
| 268 | + type: selectedSortValue.value // 1-位置 2-工单名称 3-情况描述 4-工单编号 | ||
| 269 | + }; | ||
| 270 | + | ||
| 271 | + let res; | ||
| 272 | + if (activeTab.value === 0) { | ||
| 273 | + // 待办工单 | ||
| 274 | + res = await getTodoOrderList(apiParams); | ||
| 275 | + } else { | ||
| 276 | + // 已办工单 | ||
| 277 | + res = await getDoneOrderList(apiParams); | ||
| 278 | + } | ||
| 279 | + | ||
| 280 | + // 适配z-paging分页 | ||
| 281 | + paging.value.complete(res.list, res.total); | ||
| 282 | + } catch (error) { | ||
| 283 | + console.error('加载工单失败:', error); | ||
| 284 | + paging.value?.complete(false); | ||
| 285 | + uni.showToast({ title: '加载失败,请重试', icon: 'none' }); | ||
| 286 | + } | ||
| 287 | +}; | ||
| 288 | + | ||
| 289 | +// ========== 事件处理 ========== | ||
| 290 | +// 标签页切换 | ||
| 291 | +const handleTabChange = (index) => { | ||
| 292 | + activeTab.value = index; | ||
| 293 | + paging.value?.reload(); // 切换标签页刷新列表 | ||
| 294 | +}; | ||
| 295 | + | ||
| 296 | +// 排序变更 | ||
| 297 | +const handleSortChange = (val) => { | ||
| 298 | + selectedSortValue.value = val.id; | ||
| 299 | + searchValue.value = ''; | ||
| 300 | + paging.value?.reload(); // 排序变更刷新列表 | ||
| 301 | +}; | ||
| 302 | + | ||
| 303 | +// 搜索 | ||
| 304 | +const handleSearch = (val) => { | ||
| 305 | + searchValue.value = val; | ||
| 306 | + paging.value?.reload(); // 搜索刷新列表 | ||
| 307 | +}; | ||
| 308 | + | ||
| 309 | +// 工单详情 | ||
| 310 | +const handleDetail = (item) => { | ||
| 311 | + uni.navigateTo({ | ||
| 312 | + url: `/pages-sub/daily/quick-order/order-detail?id=${item.id}` | ||
| 313 | + }); | ||
| 314 | +}; | ||
| 315 | + | ||
| 316 | +// 待办-处理工单 | ||
| 317 | +const handleProcess = async (item) => { | ||
| 318 | + try { | ||
| 319 | + // 调用处理工单接口,获取跳转标识 | ||
| 320 | + const res = await uni.request({ | ||
| 321 | + url: '/api/order/process', | ||
| 322 | + method: 'POST', | ||
| 323 | + data: { orderId: item.id } | ||
| 324 | + }); | ||
| 325 | + | ||
| 326 | + const { jumpType, jumpUrl } = res.data; | ||
| 327 | + // 根据返回标识跳转不同页面 | ||
| 328 | + if (jumpType === 1) { | ||
| 329 | + uni.navigateTo({ url: jumpUrl }); | ||
| 330 | + } else if (jumpType === 2) { | ||
| 331 | + uni.redirectTo({ url: jumpUrl }); | ||
| 332 | + } else if (jumpType === 3) { | ||
| 333 | + uni.switchTab({ url: jumpUrl }); | ||
| 334 | + } | ||
| 335 | + } catch (error) { | ||
| 336 | + console.error('处理工单失败:', error); | ||
| 337 | + uni.showToast({ title: '处理失败,请重试', icon: 'none' }); | ||
| 338 | + } | ||
| 339 | +}; | ||
| 340 | + | ||
| 341 | +// 待办-回退工单 | ||
| 342 | +const handleReject = (item) => { | ||
| 343 | + currentRejectItem.value = item; | ||
| 344 | + rejectReason.value = ''; | ||
| 345 | + rejectFileList.value = []; | ||
| 346 | + rejectPopupShow.value = true; | ||
| 347 | +}; | ||
| 348 | + | ||
| 349 | +// 确认回退工单 | ||
| 350 | +const confirmReject = async () => { | ||
| 351 | + if (!rejectReason.value.trim()) { | ||
| 352 | + uni.showToast({ title: '请填写回退原因', icon: 'none' }); | ||
| 353 | + return; | ||
| 354 | + } | ||
| 355 | + | ||
| 356 | + try { | ||
| 357 | + // 调用回退工单接口 | ||
| 358 | + await uni.request({ | ||
| 359 | + url: '/api/order/reject', | ||
| 360 | + method: 'POST', | ||
| 361 | + data: { | ||
| 362 | + orderId: currentRejectItem.value.id, | ||
| 363 | + reason: rejectReason.value, | ||
| 364 | + fileUrls: rejectFileList.value.map(file => file.url) | ||
| 365 | + } | ||
| 366 | + }); | ||
| 367 | + | ||
| 368 | + uni.showToast({ title: '回退成功', icon: 'success' }); | ||
| 369 | + rejectPopupShow.value = false; | ||
| 370 | + paging.value?.reload(); // 刷新列表 | ||
| 371 | + } catch (error) { | ||
| 372 | + console.error('回退工单失败:', error); | ||
| 373 | + uni.showToast({ title: '回退失败,请重试', icon: 'none' }); | ||
| 374 | + } | ||
| 375 | +}; | ||
| 376 | + | ||
| 377 | +// 新增工单 | ||
| 378 | +const handleAddOrder = () => { | ||
| 379 | + uni.navigateTo({ | ||
| 380 | + url: '/pages-sub/daily/quick-order/add-order' | ||
| 381 | + }); | ||
| 382 | +}; | ||
| 383 | + | ||
| 384 | +// 紧急程度标签类型转换 | ||
| 385 | +const getUrgencyType = (level) => { | ||
| 386 | + switch (level) { | ||
| 387 | + case '紧急': | ||
| 388 | + return 'danger'; | ||
| 389 | + case '重要': | ||
| 390 | + return 'warning'; | ||
| 391 | + case '普通': | ||
| 392 | + return 'info'; | ||
| 393 | + default: | ||
| 394 | + return 'default'; | ||
| 395 | + } | ||
| 396 | +}; | ||
| 397 | + | ||
| 398 | +// 上传图片-读取后 | ||
| 399 | +const handleAfterRead = (file) => { | ||
| 400 | + rejectFileList.value.push(file); | ||
| 401 | +}; | ||
| 402 | + | ||
| 403 | +// 上传图片-删除 | ||
| 404 | +const handleDeleteFile = (index) => { | ||
| 405 | + rejectFileList.value.splice(index, 1); | ||
| 406 | +}; | ||
| 407 | + | ||
| 408 | +// 页面初始化 | ||
| 409 | +onMounted(() => { | ||
| 410 | + // 初始化加载列表 | ||
| 411 | + paging.value?.reload(); | ||
| 412 | +}); | ||
| 413 | +</script> | ||
| 414 | + | ||
| 415 | +<style scoped lang="scss"> | ||
| 416 | +.page-container { | ||
| 417 | + min-height: 100vh; | ||
| 418 | + background-color: #f8f8f8; | ||
| 419 | +} | ||
| 420 | + | ||
| 421 | +// 顶部固定区域 | ||
| 422 | +.header-wrap { | ||
| 423 | + background-color: #fff; | ||
| 424 | + box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05); | ||
| 425 | +} | ||
| 426 | + | ||
| 427 | +// 搜索栏样式 | ||
| 428 | +.search-header { | ||
| 429 | + display: flex; | ||
| 430 | + align-items: center; | ||
| 431 | + padding: 20rpx; | ||
| 432 | + box-sizing: border-box; | ||
| 433 | + | ||
| 434 | + .select-wrap { | ||
| 435 | + width: 120rpx; | ||
| 436 | + margin-right: 20rpx; | ||
| 437 | + | ||
| 438 | + :deep(.u-select) { | ||
| 439 | + width: 100%; | ||
| 440 | + font-size: 28rpx; | ||
| 441 | + } | ||
| 442 | + | ||
| 443 | + :deep(.u-input__placeholder) { | ||
| 444 | + font-size: 28rpx; | ||
| 445 | + } | ||
| 446 | + } | ||
| 447 | + | ||
| 448 | + .search-input-wrap { | ||
| 449 | + flex: 1; | ||
| 450 | + } | ||
| 451 | +} | ||
| 452 | + | ||
| 453 | +// 工单卡片样式 | ||
| 454 | +.common-card-list { | ||
| 455 | + padding: 20rpx; | ||
| 456 | +} | ||
| 457 | + | ||
| 458 | +.order-card { | ||
| 459 | + margin-bottom: 20rpx; | ||
| 460 | + background-color: #fff; | ||
| 461 | + border-radius: 12rpx; | ||
| 462 | + overflow: hidden; | ||
| 463 | +} | ||
| 464 | + | ||
| 465 | +.card-body { | ||
| 466 | + padding: 20rpx; | ||
| 467 | + | ||
| 468 | + .u-body-item { | ||
| 469 | + margin-bottom: 16rpx; | ||
| 470 | + font-size: 28rpx; | ||
| 471 | + | ||
| 472 | + &:last-child { | ||
| 473 | + margin-bottom: 0; | ||
| 474 | + } | ||
| 475 | + | ||
| 476 | + .u-body-item-title { | ||
| 477 | + color: #666; | ||
| 478 | + min-width: 120rpx; | ||
| 479 | + } | ||
| 480 | + | ||
| 481 | + .u-body-value { | ||
| 482 | + color: #333; | ||
| 483 | + flex: 1; | ||
| 484 | + } | ||
| 485 | + } | ||
| 486 | + | ||
| 487 | + .mt-20 { | ||
| 488 | + margin-top: 20rpx; | ||
| 489 | + } | ||
| 490 | +} | ||
| 491 | + | ||
| 492 | +// 公共flex样式 | ||
| 493 | +.u-flex { | ||
| 494 | + display: flex; | ||
| 495 | + align-items: center; | ||
| 496 | +} | ||
| 497 | + | ||
| 498 | +.common-justify-between { | ||
| 499 | + justify-content: space-between; | ||
| 500 | +} | ||
| 501 | + | ||
| 502 | +.common-item-center { | ||
| 503 | + align-items: center; | ||
| 504 | +} | ||
| 505 | + | ||
| 506 | +// 底部按钮 | ||
| 507 | +.fixed-bottom-btn-wrap { | ||
| 508 | + position: fixed; | ||
| 509 | + bottom: 0; | ||
| 510 | + left: 0; | ||
| 511 | + right: 0; | ||
| 512 | + padding: 20rpx; | ||
| 513 | + background-color: #fff; | ||
| 514 | + box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05); | ||
| 515 | + | ||
| 516 | + :deep(.u-button) { | ||
| 517 | + width: 100%; | ||
| 518 | + height: 88rpx; | ||
| 519 | + font-size: 32rpx; | ||
| 520 | + } | ||
| 521 | +} | ||
| 522 | + | ||
| 523 | +// 回退弹窗样式 | ||
| 524 | +.reject-popup { | ||
| 525 | + width: 680rpx; | ||
| 526 | + padding: 30rpx; | ||
| 527 | + background-color: #fff; | ||
| 528 | + border-radius: 12rpx; | ||
| 529 | + | ||
| 530 | + .popup-title { | ||
| 531 | + font-size: 32rpx; | ||
| 532 | + font-weight: 600; | ||
| 533 | + color: #333; | ||
| 534 | + } | ||
| 535 | + | ||
| 536 | + .upload-title { | ||
| 537 | + font-size: 28rpx; | ||
| 538 | + color: #666; | ||
| 539 | + margin-bottom: 10rpx; | ||
| 540 | + } | ||
| 541 | + | ||
| 542 | + .popup-btn-wrap { | ||
| 543 | + display: flex; | ||
| 544 | + justify-content: flex-end; | ||
| 545 | + } | ||
| 546 | +} | ||
| 547 | +</style> | ||
| 0 | \ No newline at end of file | 548 | \ No newline at end of file |
pages.json
| @@ -119,8 +119,9 @@ | @@ -119,8 +119,9 @@ | ||
| 119 | { | 119 | { |
| 120 | "root": "pages-sub/problem", | 120 | "root": "pages-sub/problem", |
| 121 | "pages": [ | 121 | "pages": [ |
| 122 | + | ||
| 122 | { | 123 | { |
| 123 | - "path": "order-manage/index", | 124 | + "path": "work-order-manage/index", |
| 124 | "style": { "navigationBarTitleText": "工单管理" } | 125 | "style": { "navigationBarTitleText": "工单管理" } |
| 125 | }, | 126 | }, |
| 126 | { | 127 | { |
pages/login/index.vue
| @@ -308,30 +308,4 @@ const handleLogin = async () => { | @@ -308,30 +308,4 @@ const handleLogin = async () => { | ||
| 308 | } | 308 | } |
| 309 | } | 309 | } |
| 310 | 310 | ||
| 311 | -// 登录按钮 | ||
| 312 | -.login-btn { | ||
| 313 | - width: 100%; | ||
| 314 | - margin-top: 20rpx; | ||
| 315 | - | ||
| 316 | - // 按钮点击反馈 | ||
| 317 | - &:active { | ||
| 318 | - transform: scale(0.98); | ||
| 319 | - transition: transform 0.1s ease; | ||
| 320 | - } | ||
| 321 | -} | ||
| 322 | - | ||
| 323 | -// 覆盖uview默认样式 | ||
| 324 | -:deep(.up-input) { | ||
| 325 | - border-radius: 8rpx !important; | ||
| 326 | - height: 80rpx !important; | ||
| 327 | - line-height: 80rpx !important; | ||
| 328 | -} | ||
| 329 | - | ||
| 330 | -:deep(.up-input__prefix) { | ||
| 331 | - margin-right: 15rpx !important; | ||
| 332 | -} | ||
| 333 | - | ||
| 334 | -:deep(.up-button__loading) { | ||
| 335 | - color: #fff !important; | ||
| 336 | -} | ||
| 337 | </style> | 311 | </style> |
| 338 | \ No newline at end of file | 312 | \ No newline at end of file |