Commit 7b1f488f9c67e39bceb23c97b9d5c7379e937cb9

Authored by 刘淇
1 parent d768bdcf

封装下图片上传

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&lt;UniFormRef&gt;(null) @@ -170,11 +163,7 @@ const workOrderFormRef = ref&lt;UniFormRef&gt;(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 () =&gt; { @@ -308,30 +308,4 @@ const handleLogin = async () =&gt; {
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