Commit f0ec74d652d5a10dab291978081b2791c4210886

Authored by 刘淇
1 parent 3d474e84

大区经理派单

api/work-order-manage/work-order-manage.js
... ... @@ -113,15 +113,15 @@ export const workorderCreate = (data) => {
113 113  
114 114  
115 115 /**
116   - * 大区经理问题工单创建
  116 + * 问题工单创建 大区经理 全域巡查员 ai经理 督查员
117 117 * @returns {Promise}
118 118 */
119   -export const regionmgrWorkorderCreat = (data) => {
120   - return post('/app-api/bpm/regionmgr/workorder',data);
  119 +export const regionmgrWorkorderCreate = (data) => {
  120 + return post('/app-api/bpm/regionmgr/workorder/create',data);
121 121 };
122 122  
123 123 /**
124   - * app端统一审批入口 -- 大区经理
  124 + * app端统一审批入口 -- 大区经理 全域巡查员 ai经理 督查员
125 125 * @returns {Promise}
126 126 */
127 127 export const regionmgrUniversalApproval = (params) => {
... ...
common/utils/common.js
... ... @@ -44,17 +44,17 @@ export const nextStepMap = {
44 44  
45 45  
46 46 export const buzStatusMap = {
47   - '000' :'巡查员发起',
48   - "210" : '养护组长退回',
49   - "110" : '养护组长分配',
50   - "200" : '巡查员结束工单',
51   - "100" : '巡查员重新发起',
52   - "220" : '养护员退回',
53   - "120" : '养护员实施',
54   - "130" : '养护组长验收通过',
55   - "230" : '养护组长验收不通过',
56   - "140" : '巡查员验收通过',
57   - "240" : '巡查员验收不通过',
  47 + '000' :'发起',
  48 + "210" : '退回',
  49 + "110" : '分配',
  50 + "200" : '结束工单',
  51 + "100" : '重新发起',
  52 + "220" : '退回',
  53 + "120" : '实施',
  54 + "130" : '验收通过',
  55 + "230" : '验收不通过',
  56 + "140" : '验收通过',
  57 + "240" : '验收不通过',
58 58 }
59 59  
60 60  
... ...
pages-sub/problem/regional-order-manage/add-order.vue
1   -<script lang="ts">
2   -import {defineComponent} from 'vue'
  1 +<template>
  2 + <view class="page-container">
  3 + <view class="work-order-form-content commonPageLRpadding">
  4 + <up-form
  5 + label-position="left"
  6 + :model="workOrderForm"
  7 + ref="workOrderFormRef"
  8 + labelWidth="200rpx"
  9 + >
  10 + <!-- 1. 工单位置(地图选择) -->
  11 + <up-form-item
  12 + label="工单位置"
  13 + prop="workLocation"
  14 + border-bottom
  15 + required
  16 + @click="chooseWorkLocation(); hideKeyboard()"
  17 + >
  18 + <up-input
  19 + v-model="workOrderForm.workLocation"
  20 + border="none"
  21 + readonly
  22 + suffix-icon="map-fill"
  23 + placeholder="点击选择工单位置"
  24 + ></up-input>
  25 + </up-form-item>
  26 +
  27 + <!-- 2. 工单名称(下拉框) -->
  28 + <up-form-item
  29 + label="工单名称"
  30 + prop="orderName"
  31 + border-bottom
  32 + required
  33 + @click="handleActionSheetOpen('orderName'); hideKeyboard()"
  34 + >
  35 + <up-input
  36 + v-model="workOrderForm.orderName"
  37 + disabled
  38 + disabled-color="#ffffff"
  39 + placeholder="请选择工单名称"
  40 + border="none"
  41 + ></up-input>
  42 + <template #right>
  43 + <up-icon name="arrow-right" size="16"></up-icon>
  44 + </template>
  45 + </up-form-item>
  46 +
  47 + <!-- 3. 情况描述(文本域) -->
  48 + <up-form-item
  49 + label="情况描述"
  50 + prop="problemDesc"
  51 + required
  52 + >
  53 + <up-textarea
  54 + placeholder="请输入情况描述(最多200字)"
  55 + v-model.trim="workOrderForm.problemDesc"
  56 + count
  57 + maxlength="200"
  58 + rows="4"
  59 + @blur="() => workOrderFormRef.validateField('problemDesc')"
  60 + ></up-textarea>
  61 + </up-form-item>
  62 +
  63 + <!-- 4. 问题照片 -->
  64 + <up-form-item label="问题照片" prop="problemImgs" required>
  65 + <up-upload
  66 + :file-list="problemImgs.imgList.value||[]"
  67 + @after-read="problemImgs.uploadImgs"
  68 + @delete="problemImgs.deleteImg"
  69 + multiple
  70 + :width="70"
  71 + :height="70"
  72 + :max-count="problemImgs.uploadConfig.maxCount"
  73 + :upload-text="problemImgs.uploadConfig.uploadText"
  74 + :size-type="problemImgs.uploadConfig.sizeType"
  75 + ></up-upload>
  76 + </up-form-item>
  77 +
  78 + <!-- 派单情况分组(放在问题照片下方,增加间距) -->
  79 +<!-- <up-gap height="20" bgColor="#bbb"></up-gap>-->
  80 + <view class="dispatch-group">
  81 + <view class="dispatch-title">派单情况</view>
  82 +
  83 + <!-- 业务线单选框 -->
  84 + <up-form-item
  85 + label="业务线"
  86 + prop="busiLineCn"
  87 + border-bottom
  88 + required
  89 + >
  90 + <up-radio-group
  91 + v-model="workOrderForm.busiLineCn"
  92 + placement="row"
  93 + @change="handleBusiLineChange"
  94 + >
  95 + <up-radio
  96 + v-for="item in busiLineOptions"
  97 + :key="item.name"
  98 + :label="item.name"
  99 + :name="item.name"
  100 + ></up-radio>
  101 + </up-radio-group>
  102 + </up-form-item>
  103 +
  104 + <!-- 道路名称(下拉框) -->
  105 + <up-form-item
  106 + label="道路名称"
  107 + prop="roadName"
  108 + border-bottom
  109 + required
  110 + @click="handleActionSheetOpen('roadName'); hideKeyboard()"
  111 + >
  112 + <up-input
  113 + v-model="workOrderForm.roadName"
  114 + readonly
  115 + disabled-color="#ffffff"
  116 + placeholder="请先选择工单位置"
  117 + border="none"
  118 + ></up-input>
  119 + <template #right>
  120 + <up-icon name="arrow-right" size="16" ></up-icon>
  121 + </template>
  122 + </up-form-item>
  123 +
  124 + <!-- 紧急程度选择 -->
  125 + <up-form-item
  126 + label="紧急程度"
  127 + prop="pressingTypeName"
  128 + border-bottom
  129 + required
  130 + @click="handleActionSheetOpen('pressingType'); hideKeyboard()"
  131 + >
  132 + <up-input
  133 + v-model="workOrderForm.pressingTypeName"
  134 + disabled
  135 + disabled-color="#ffffff"
  136 + placeholder="请选择紧急程度"
  137 + border="none"
  138 + ></up-input>
  139 + <template #right>
  140 + <up-icon name="arrow-right" size="16"></up-icon>
  141 + </template>
  142 + </up-form-item>
  143 + </view>
  144 +
  145 + <!-- 5. 完成时间 -->
  146 + <up-form-item
  147 + label="希望完成时间"
  148 + prop="expectedFinishDate"
  149 + @click="show=true;hideKeyboard()"
  150 + >
  151 + <up-input
  152 + v-model="workOrderForm.expectedFinishDate"
  153 + border="none"
  154 + readonly
  155 + placeholder="点击选择时间"
  156 + ></up-input>
  157 + <template #right>
  158 + <view v-if="workOrderForm.expectedFinishDate" @click.stop>
  159 + <up-icon
  160 + name="close"
  161 + size="16"
  162 + @click.stop="clearExpectedFinishDate"
  163 + ></up-icon>
  164 + </view>
  165 + <up-icon name="arrow-right" size="16" v-else></up-icon>
  166 + </template>
  167 + </up-form-item>
  168 + </up-form>
  169 + </view>
  170 +
  171 + <!-- 底部提交按钮 -->
  172 + <view class="fixed-bottom-btn-wrap">
  173 + <up-button
  174 + type="primary"
  175 + text="提交工单"
  176 + @click="submitWorkOrder"
  177 + ></up-button>
  178 + </view>
  179 +
  180 + <!-- 合并后的通用下拉弹窗 -->
  181 + <up-action-sheet
  182 + :show="showActionSheet"
  183 + :actions="currentActionSheetData.list"
  184 + :title="currentActionSheetData.title"
  185 + @close="handleActionSheetClose"
  186 + @select="handleActionSheetSelect"
  187 + ></up-action-sheet>
  188 +
  189 + <!-- 完成时间选择器 -->
  190 + <up-datetime-picker
  191 + :show="show"
  192 + v-model="expectedFinishDate"
  193 + mode="datetime"
  194 + :min-date="new Date()"
  195 + @cancel="show = false"
  196 + @confirm="expectedFinishDateConfirm"
  197 + ></up-datetime-picker>
  198 + </view>
  199 +</template>
  200 +
  201 +<script setup>
  202 +import { ref, reactive } from 'vue'
  203 +import { onReady, onShow, onLoad } from '@dcloudio/uni-app';
  204 +import { useUploadImgs } from '@/common/utils/useUploadImgs'
  205 +import { getRoadListByLatLng } from '@/api/common'
  206 +import { regionmgrUniversalApproval, regionmgrWorkorderCreate } from '@/api/work-order-manage/work-order-manage'
  207 +import { timeFormat } from '@/uni_modules/uview-plus'
  208 +import { nextStepMap } from '@/common/utils/common'
  209 +import { useUserStore } from '@/pinia/user';
  210 +
  211 +// ========== 状态管理 ==========
  212 +const userStore = useUserStore();
  213 +
  214 +// ========== 业务线相关状态 ==========
  215 +// 业务线映射表
  216 +const busiLineMap = ref({
  217 + 'yl': '园林',
  218 + 'sz': '市政',
  219 + 'wy': '物业',
  220 + '园林': 'yl',
  221 + '市政': 'sz',
  222 + '物业': 'wy'
  223 +});
  224 +
  225 +// 业务线选项列表
  226 +const busiLineOptions = ref([]);
  227 +const formatBusiLineOptions = () => {
  228 + if (!userStore.userInfo?.user?.busiLine) {
  229 + busiLineOptions.value = [];
  230 + return;
  231 + }
  232 + const rawBusiLines = userStore.userInfo.user.busiLine.split(',');
  233 + busiLineOptions.value = rawBusiLines.map(item => ({
  234 + name: busiLineMap.value[item.trim()]
  235 + }));
  236 +};
3 237  
4   -export default defineComponent({
5   - name: "add-order"
  238 +// 工具方法:通过中文名称获取对应的英文标识
  239 +const getBusiLineEnByCn = (cnName) => {
  240 + return busiLineMap.value[cnName] || '';
  241 +};
  242 +
  243 +// ========== 表单Ref ==========
  244 +const workOrderFormRef = ref(null)
  245 +
  246 +// ========== 公共上传逻辑复用 ==========
  247 +const problemImgs = useUploadImgs({
  248 + maxCount: 3,
  249 + uploadText: '选择问题照片',
  250 + sizeType: ['compressed'],
  251 + formRef: workOrderFormRef,
  252 + fieldName: 'problemImgs'
  253 +})
  254 +
  255 +if (!Array.isArray(problemImgs.rawImgList.value)) {
  256 + problemImgs.rawImgList.value = [];
  257 +}
  258 +
  259 +// ========== 页面状态 ==========
  260 +const showActionSheet = ref(false)
  261 +const currentActionSheetData = reactive({
  262 + type: '',
  263 + list: [],
  264 + title: ''
  265 +})
  266 +const show = ref(false)
  267 +const expectedFinishDate = ref(Date.now())
  268 +
  269 +// ========== 重新提交相关状态 ==========
  270 +const isRenew = ref(false);
  271 +const renewOrderData = ref(null);
  272 +
  273 +// ========== 下拉列表数据 ==========
  274 +const roadNameList = ref([])
  275 +const orderNameList = ref([])
  276 +const pressingTypeList = ref([])
  277 +
  278 +// ========== 工单表单数据 ==========
  279 +const workOrderForm = reactive({
  280 + busiLineCn: '',
  281 + roadId: 0,
  282 + roadName: '',
  283 + workLocation: '',
  284 + orderName: '',
  285 + pressingType: '',
  286 + pressingTypeName: '',
  287 + problemDesc: '',
  288 + lat: 0,
  289 + lon: 0,
  290 + expectedFinishDate: '',
6 291 })
  292 +
  293 +// ========== 表单校验规则 ==========
  294 +const workOrderFormRules = reactive({
  295 + busiLineCn: [
  296 + { type: 'string', required: true, message: '请选择业务线', trigger: ['change', 'blur'] }
  297 + ],
  298 + workLocation: [
  299 + { type: 'string', required: true, message: '请选择工单位置', trigger: ['change', 'blur'] }
  300 + ],
  301 + roadName: [
  302 + { type: 'string', required: true, message: '请选择道路名称', trigger: ['change', 'blur'] }
  303 + ],
  304 + orderName: [
  305 + { type: 'string', required: true, message: '请选择工单名称', trigger: ['change', 'blur'] }
  306 + ],
  307 + pressingTypeName: [
  308 + { type: 'string', required: true, message: '请选择紧急程度', trigger: ['change'] }
  309 + ],
  310 + problemDesc: [
  311 + { type: 'string', required: true, message: '请输入情况描述', trigger: ['change', 'blur'] },
  312 + { type: 'string', min: 3, max: 200, message: '情况描述需3-200字', trigger: ['change', 'blur'] }
  313 + ],
  314 + problemImgs: [problemImgs.imgValidateRule]
  315 +})
  316 +
  317 +// ========== 生命周期 ==========
  318 +onLoad((options) => {
  319 + // 初始化业务线选项
  320 + formatBusiLineOptions();
  321 + // 默认选中第一个业务线
  322 + if (busiLineOptions.value.length > 0) {
  323 + workOrderForm.busiLineCn = busiLineOptions.value[0].name;
  324 + }
  325 +
  326 + // 判断是否为重新提交状态
  327 + if (options.isRenew == 1 && options.tempKey) {
  328 + isRenew.value = true;
  329 + const tempKey = options.tempKey;
  330 +
  331 + try {
  332 + const orderData = uni.getStorageSync(tempKey);
  333 + if (orderData && typeof orderData === 'object') {
  334 + renewOrderData.value = orderData;
  335 + echoOrderData(renewOrderData.value);
  336 + } else {
  337 + uni.showToast({ title: '工单数据不存在,无法重新提交', icon: 'none' });
  338 + setTimeout(() => uni.navigateBack(), 1000);
  339 + return;
  340 + }
  341 + } catch (error) {
  342 + console.error('读取工单数据失败:', error);
  343 + uni.showToast({ title: '数据读取异常,无法重新提交', icon: 'none' });
  344 + setTimeout(() => uni.navigateBack(), 1000);
  345 + return;
  346 + } finally {
  347 + uni.removeStorageSync(tempKey);
  348 + }
  349 + }
  350 +});
  351 +
  352 +onReady(() => {
  353 + if (workOrderFormRef.value) {
  354 + workOrderFormRef.value.setRules(workOrderFormRules)
  355 + }
  356 + console.log('工单表单规则初始化完成')
  357 +})
  358 +
  359 +onShow(() => {
  360 + // 初始化工单名称列表
  361 + orderNameList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('work_name'))
  362 + // 初始化紧急程度列表
  363 + pressingTypeList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('workorder_pressing_type'))
  364 +})
  365 +
  366 +// ========== 核心方法 ==========
  367 +const echoOrderData = (orderItem) => {
  368 + // 回显业务线
  369 + if (orderItem.busiLine) {
  370 + workOrderForm.busiLineCn = busiLineMap.value[orderItem.busiLine];
  371 + }
  372 +
  373 + // 回显基础字段
  374 + workOrderForm.roadId = orderItem.roadId || 0;
  375 + workOrderForm.roadName = orderItem.roadName || '';
  376 + workOrderForm.workLocation = orderItem.lonLatAddress || orderItem.roadName || '';
  377 + workOrderForm.orderName = orderItem.orderName || '';
  378 + workOrderForm.pressingType = orderItem.pressingType || '';
  379 + workOrderForm.pressingTypeName = uni.$dict.getDictLabel('workorder_pressing_type', orderItem.pressingType) || '';
  380 + workOrderForm.problemDesc = orderItem.remark || '';
  381 + workOrderForm.lat = orderItem.lat || 0;
  382 + workOrderForm.lon = orderItem.lon || 0;
  383 + workOrderForm.expectedFinishDate = timeFormat(orderItem.expectedFinishDate, 'yyyy-mm-dd hh:MM:ss') || timeFormat(new Date(), 'yyyy-mm-dd hh:MM:ss');
  384 +
  385 + // 回显图片
  386 + if (orderItem.problemsImgs && Array.isArray(orderItem.problemsImgs) && orderItem.problemsImgs.length > 0) {
  387 + const imgList = orderItem.problemsImgs.map((imgUrl, index) => ({
  388 + url: imgUrl,
  389 + name: `renew_img_${index}`,
  390 + status: 'success'
  391 + }));
  392 + problemImgs.imgList.value = imgList;
  393 + problemImgs.rawImgList.value = imgList;
  394 + }
  395 +
  396 + // 自动获取道路列表
  397 + if (orderItem.lat && orderItem.lon) {
  398 + getRoadListByBusiLine();
  399 + }
  400 +};
  401 +
  402 +// 业务线切换事件
  403 +const handleBusiLineChange = () => {
  404 + workOrderForm.roadName = '';
  405 + workOrderForm.roadId = 0;
  406 + roadNameList.value = [];
  407 + if (workOrderForm.workLocation) {
  408 + getRoadListByBusiLine();
  409 + }
  410 +};
  411 +
  412 +// 获取道路列表(带业务线)
  413 +const getRoadListByBusiLine = async () => {
  414 + if (!workOrderForm.lat || !workOrderForm.lon) {
  415 + return;
  416 + }
  417 + const busiLineEn = getBusiLineEnByCn(workOrderForm.busiLineCn);
  418 + if (!busiLineEn) {
  419 + uni.showToast({ title: '业务线标识异常', icon: 'none' });
  420 + return;
  421 + }
  422 +
  423 + try {
  424 + uni.showLoading({ title: '获取道路名称中...' });
  425 + const roadRes = await getRoadListByLatLng({
  426 + busiLine: busiLineEn,
  427 + latitude: workOrderForm.lat,
  428 + longitude: workOrderForm.lon
  429 + });
  430 + uni.hideLoading();
  431 + if (Array.isArray(roadRes)) {
  432 + roadNameList.value = roadRes.map((item) => ({
  433 + name: item.roadName || '',
  434 + code: item.roadCode || '',
  435 + id: item.roadId || 0
  436 + }));
  437 + } else {
  438 + roadNameList.value = [{ name: '未查询到道路名称', code: '', id: 0 }];
  439 + uni.showToast({ title: '未查询到该位置的道路信息', icon: 'none' });
  440 + }
  441 + } catch (err) {
  442 + uni.hideLoading();
  443 + console.error('获取道路名称失败:', err);
  444 + uni.showToast({ title: '获取道路名称失败,请重试', icon: 'none' });
  445 + roadNameList.value = [{ name: '获取失败,请重新选择位置', code: '', id: 0 }];
  446 + }
  447 +};
  448 +
  449 +// ========== 通用弹窗方法 ==========
  450 +const handleActionSheetOpen = (type) => {
  451 + if (type === 'roadName' && !workOrderForm.workLocation) {
  452 + uni.showToast({ title: '请先选择工单位置', icon: 'none' })
  453 + return
  454 + }
  455 +
  456 + const configMap = {
  457 + roadName: {
  458 + title: '请选择道路名称',
  459 + list: roadNameList.value
  460 + },
  461 + orderName: {
  462 + title: '请选择工单名称',
  463 + list: orderNameList.value
  464 + },
  465 + pressingType: {
  466 + title: '请选择紧急程度',
  467 + list: pressingTypeList.value
  468 + }
  469 + }
  470 +
  471 + currentActionSheetData.type = type
  472 + currentActionSheetData.title = configMap[type].title
  473 + currentActionSheetData.list = configMap[type].list
  474 + showActionSheet.value = true
  475 +}
  476 +
  477 +const handleActionSheetClose = () => {
  478 + showActionSheet.value = false
  479 + currentActionSheetData.type = ''
  480 + currentActionSheetData.list = []
  481 + currentActionSheetData.title = ''
  482 +}
  483 +
  484 +const handleActionSheetSelect = (e) => {
  485 + const { type } = currentActionSheetData
  486 + switch (type) {
  487 + case 'roadName':
  488 + workOrderForm.roadName = e.name
  489 + workOrderForm.roadId = e.code
  490 + workOrderFormRef.value?.validateField('roadName')
  491 + break
  492 + case 'orderName':
  493 + workOrderForm.orderName = e.name
  494 + workOrderFormRef.value?.validateField('orderName')
  495 + break
  496 + case 'pressingType':
  497 + workOrderForm.pressingType = e.value
  498 + workOrderForm.pressingTypeName = e.name
  499 + workOrderFormRef.value?.validateField('pressingTypeName')
  500 + break
  501 + }
  502 + showActionSheet.value = false
  503 +}
  504 +
  505 +const navigateBack = () => {
  506 + uni.reLaunch({
  507 + url: '/pages-sub/problem/work-order-manage/index',
  508 + fail: () => {
  509 + uni.navigateBack({ delta: 2 });
  510 + }
  511 + });
  512 +}
  513 +
  514 +// 清除希望完成时间
  515 +const clearExpectedFinishDate = ()=> {
  516 + workOrderForm.expectedFinishDate = ''
  517 +}
  518 +
  519 +// 选择工单位置
  520 +const chooseWorkLocation = () => {
  521 + uni.chooseLocation({
  522 + success: async (res) => {
  523 + workOrderForm.roadName = ''
  524 + workOrderForm.roadId = 0
  525 + roadNameList.value = []
  526 +
  527 + workOrderForm.workLocation = res.name
  528 + workOrderForm.lat = res.latitude
  529 + workOrderForm.lon = res.longitude
  530 +
  531 + workOrderFormRef.value?.validateField('workLocation')
  532 + workOrderFormRef.value?.validateField('roadName')
  533 +
  534 + await getRoadListByBusiLine();
  535 + },
  536 + fail: (err) => {
  537 + console.error('选择位置失败:', err)
  538 + uni.showToast({ title: '选择位置失败:' + err.errMsg, icon: 'none' })
  539 + }
  540 + })
  541 +}
  542 +
  543 +// 完成时间确认
  544 +const expectedFinishDateConfirm = (e) => {
  545 + workOrderForm.expectedFinishDate = timeFormat(e.value, 'yyyy-mm-dd hh:MM:ss')
  546 + show.value = false
  547 +}
  548 +
  549 +// 隐藏键盘
  550 +const hideKeyboard = () => {
  551 + uni.hideKeyboard()
  552 +}
  553 +
  554 +// 提交工单
  555 +const submitWorkOrder = async () => {
  556 + try {
  557 + await workOrderFormRef.value.validate()
  558 +
  559 + const busiLineEn = getBusiLineEnByCn(workOrderForm.busiLineCn);
  560 + if (!busiLineEn) {
  561 + uni.showToast({ title: '业务线选择异常,请重新选择', icon: 'none' });
  562 + return;
  563 + }
  564 +
  565 + const commonSubmitData = {
  566 + roadId: workOrderForm.roadId,
  567 + roadName: workOrderForm.roadName,
  568 + problemsImgs: problemImgs.getSuccessImgUrls(),
  569 + remark: workOrderForm.problemDesc.trim(),
  570 + latLonType: 2,
  571 + lat: workOrderForm.lat,
  572 + lon: workOrderForm.lon,
  573 + lonLatAddress: workOrderForm.workLocation,
  574 + pressingType: workOrderForm.pressingType,
  575 + orderName: workOrderForm.orderName,
  576 + expectedFinishDate: new Date(workOrderForm.expectedFinishDate).getTime(),
  577 + sourceId: 1,
  578 + sourceName: workOrderForm.busiLineCn,
  579 + busiLine: busiLineEn
  580 + }
  581 +
  582 + uni.showLoading({ title: '提交中...' })
  583 + let res
  584 +
  585 + if (isRenew.value) {
  586 + const renewSubmitData = {
  587 + workerDataId: renewOrderData.value.id,
  588 + taskKey: renewOrderData.value.taskKey,
  589 + taskId: renewOrderData.value.taskId,
  590 + operateType: nextStepMap[renewOrderData.value.taskKey]?.operateTypeRenew || '',
  591 + agree: 0,
  592 + reason: '重新提交工单',
  593 + ...commonSubmitData
  594 + }
  595 + res = await regionmgrUniversalApproval(renewSubmitData)
  596 + } else {
  597 + res = await regionmgrWorkorderCreate(commonSubmitData)
  598 + }
  599 +
  600 + uni.hideLoading()
  601 + uni.showToast({
  602 + title: isRenew.value ? '重新提交成功' : '工单提交成功',
  603 + icon: 'success',
  604 + duration: 1000
  605 + })
  606 +
  607 + setTimeout(() => {
  608 + uni.reLaunch({
  609 + url: '/pages-sub/problem/regional-order-manage/index'
  610 + })
  611 + }, 1000)
  612 + } catch (error) {
  613 + uni.hideLoading()
  614 +
  615 + if (!Array.isArray(error)) {
  616 + console.error(isRenew.value ? '工单重新提交失败:' : '工单提交失败:', error)
  617 + uni.showToast({
  618 + title: isRenew.value ? error.msg : error.msg,
  619 + icon: 'none',
  620 + duration: 2000
  621 + })
  622 + }
  623 + }
  624 +}
7 625 </script>
8 626  
9   -<template>
  627 +<style lang="scss" scoped>
  628 +// 全局页面样式
  629 +.page-container {
  630 + min-height: 100vh;
  631 + padding-bottom: 100rpx; // 给底部按钮留空间
  632 +}
10 633  
11   -</template>
  634 +// 工单表单内容容器
  635 +.work-order-form-content {
  636 + background: #fff;
  637 +}
  638 +
  639 +// 派单情况分组样式(核心:增加顶部间距)
  640 +.dispatch-group {
  641 +
  642 + padding-top: 20rpx; // 分组内顶部内边距,增强视觉区分
12 643  
13   -<style scoped lang="scss">
  644 +}
14 645  
  646 +// 派单情况标题样式
  647 +.dispatch-title {
  648 + font-size: 32rpx;
  649 + font-weight: 600;
  650 + color:$u-primary;
  651 + margin-bottom: 20rpx;
  652 + padding-left: 10rpx;
  653 +}
15 654 </style>
16 655 \ No newline at end of file
... ...
pages-sub/problem/regional-order-manage/add-patrol-order.vue 0 → 100644
  1 +<template>
  2 + <view class="page-container">
  3 + <view class="work-order-form-content commonPageLRpadding">
  4 + <up-form
  5 + label-position="left"
  6 + :model="workOrderForm"
  7 + ref="workOrderFormRef"
  8 + labelWidth="200rpx"
  9 + >
  10 + <!-- 1. 工单位置(地图选择) -->
  11 + <up-form-item
  12 + label="工单位置1"
  13 + prop="workLocation"
  14 + border-bottom
  15 + required
  16 + @click="chooseWorkLocation(); hideKeyboard()"
  17 + >
  18 + <up-input
  19 + v-model="workOrderForm.workLocation"
  20 + border="none"
  21 + readonly
  22 + suffix-icon="map-fill"
  23 + placeholder="点击选择工单位置"
  24 + ></up-input>
  25 + </up-form-item>
  26 +
  27 + <!-- 2. 工单名称(下拉框) -->
  28 + <up-form-item
  29 + label="工单名称"
  30 + prop="orderName"
  31 + border-bottom
  32 + required
  33 + @click="handleActionSheetOpen('orderName'); hideKeyboard()"
  34 + >
  35 + <up-input
  36 + v-model="workOrderForm.orderName"
  37 + disabled
  38 + disabled-color="#ffffff"
  39 + placeholder="请选择工单名称"
  40 + border="none"
  41 + ></up-input>
  42 + <template #right>
  43 + <up-icon name="arrow-right" size="16"></up-icon>
  44 + </template>
  45 + </up-form-item>
  46 +
  47 + <!-- 3. 情况描述(文本域) -->
  48 + <up-form-item
  49 + label="情况描述"
  50 + prop="problemDesc"
  51 + required
  52 + >
  53 + <up-textarea
  54 + placeholder="请输入情况描述(最多200字)"
  55 + v-model.trim="workOrderForm.problemDesc"
  56 + count
  57 + maxlength="200"
  58 + rows="4"
  59 + @blur="() => workOrderFormRef.validateField('problemDesc')"
  60 + ></up-textarea>
  61 + </up-form-item>
  62 +
  63 + <!-- 4. 问题照片 -->
  64 + <up-form-item label="问题照片" prop="problemImgs" required>
  65 + <up-upload
  66 + :file-list="problemImgs.imgList.value||[]"
  67 + @after-read="problemImgs.uploadImgs"
  68 + @delete="problemImgs.deleteImg"
  69 + multiple
  70 + :width="70"
  71 + :height="70"
  72 + :max-count="problemImgs.uploadConfig.maxCount"
  73 + :upload-text="problemImgs.uploadConfig.uploadText"
  74 + :size-type="problemImgs.uploadConfig.sizeType"
  75 + ></up-upload>
  76 + </up-form-item>
  77 +
  78 + </up-form>
  79 + </view>
  80 +
  81 + <!-- 底部提交按钮 -->
  82 + <view class="fixed-bottom-btn-wrap">
  83 + <up-button
  84 + type="primary"
  85 + text="提交工单"
  86 + @click="submitWorkOrder"
  87 + ></up-button>
  88 + </view>
  89 +
  90 + <!-- 合并后的通用下拉弹窗 -->
  91 + <up-action-sheet
  92 + :show="showActionSheet"
  93 + :actions="currentActionSheetData.list"
  94 + :title="currentActionSheetData.title"
  95 + @close="handleActionSheetClose"
  96 + @select="handleActionSheetSelect"
  97 + ></up-action-sheet>
  98 +
  99 + </view>
  100 +</template>
  101 +
  102 +<script setup>
  103 +import { ref, reactive } from 'vue'
  104 +import { onReady, onShow, onLoad } from '@dcloudio/uni-app';
  105 +import { useUploadImgs } from '@/common/utils/useUploadImgs'
  106 +import { regionmgrUniversalApproval, regionmgrWorkorderCreate } from '@/api/work-order-manage/work-order-manage'
  107 +import { timeFormat } from '@/uni_modules/uview-plus'
  108 +import { nextStepMap } from '@/common/utils/common'
  109 +import { useUserStore } from '@/pinia/user';
  110 +
  111 +// ========== 状态管理 ==========
  112 +const userStore = useUserStore();
  113 +
  114 +// ========== 业务线相关状态 ==========
  115 +// 业务线映射表
  116 +const busiLineMap = ref({
  117 + 'yl': '园林',
  118 + 'sz': '市政',
  119 + 'wy': '物业',
  120 + '园林': 'yl',
  121 + '市政': 'sz',
  122 + '物业': 'wy'
  123 +});
  124 +
  125 +// 业务线选项列表
  126 +const busiLineOptions = ref([]);
  127 +const formatBusiLineOptions = () => {
  128 + if (!userStore.userInfo?.user?.busiLine) {
  129 + busiLineOptions.value = [];
  130 + return;
  131 + }
  132 + const rawBusiLines = userStore.userInfo.user.busiLine.split(',');
  133 + busiLineOptions.value = rawBusiLines.map(item => ({
  134 + name: busiLineMap.value[item.trim()]
  135 + }));
  136 +};
  137 +
  138 +// 工具方法:通过中文名称获取对应的英文标识
  139 +const getBusiLineEnByCn = (cnName) => {
  140 + return busiLineMap.value[cnName] || '';
  141 +};
  142 +
  143 +// ========== 表单Ref ==========
  144 +const workOrderFormRef = ref(null)
  145 +
  146 +// ========== 公共上传逻辑复用 ==========
  147 +const problemImgs = useUploadImgs({
  148 + maxCount: 3,
  149 + uploadText: '选择问题照片',
  150 + sizeType: ['compressed'],
  151 + formRef: workOrderFormRef,
  152 + fieldName: 'problemImgs'
  153 +})
  154 +
  155 +if (!Array.isArray(problemImgs.rawImgList.value)) {
  156 + problemImgs.rawImgList.value = [];
  157 +}
  158 +
  159 +// ========== 页面状态 ==========
  160 +const showActionSheet = ref(false)
  161 +const currentActionSheetData = reactive({
  162 + type: '',
  163 + list: [],
  164 + title: ''
  165 +})
  166 +
  167 +// ========== 重新提交相关状态 ==========
  168 +const isRenew = ref(false);
  169 +const renewOrderData = ref(null);
  170 +
  171 +// ========== 下拉列表数据 ==========
  172 +const orderNameList = ref([])
  173 +
  174 +// ========== 工单表单数据 ==========
  175 +const workOrderForm = reactive({
  176 + orderName: '',
  177 + problemDesc: '',
  178 + lat: 0,
  179 + lon: 0,
  180 + workLocation: '',
  181 +})
  182 +
  183 +// ========== 表单校验规则 ==========
  184 +const workOrderFormRules = reactive({
  185 + workLocation: [
  186 + { type: 'string', required: true, message: '请选择工单位置', trigger: ['change', 'blur'] }
  187 + ],
  188 + orderName: [
  189 + { type: 'string', required: true, message: '请选择工单名称', trigger: ['change', 'blur'] }
  190 + ],
  191 + problemDesc: [
  192 + { type: 'string', required: true, message: '请输入情况描述', trigger: ['change', 'blur'] },
  193 + { type: 'string', min: 3, max: 200, message: '情况描述需3-200字', trigger: ['change', 'blur'] }
  194 + ],
  195 + problemImgs: [problemImgs.imgValidateRule]
  196 +})
  197 +
  198 +// ========== 生命周期 ==========
  199 +onLoad((options) => {
  200 + // 初始化业务线选项
  201 + formatBusiLineOptions();
  202 +
  203 + // 判断是否为重新提交状态
  204 + if (options.isRenew == 1 && options.tempKey) {
  205 + isRenew.value = true;
  206 + const tempKey = options.tempKey;
  207 +
  208 + try {
  209 + const orderData = uni.getStorageSync(tempKey);
  210 + if (orderData && typeof orderData === 'object') {
  211 + renewOrderData.value = orderData;
  212 + echoOrderData(renewOrderData.value);
  213 + } else {
  214 + uni.showToast({ title: '工单数据不存在,无法重新提交', icon: 'none' });
  215 + setTimeout(() => uni.navigateBack(), 1000);
  216 + return;
  217 + }
  218 + } catch (error) {
  219 + console.error('读取工单数据失败:', error);
  220 + uni.showToast({ title: '数据读取异常,无法重新提交', icon: 'none' });
  221 + setTimeout(() => uni.navigateBack(), 1000);
  222 + return;
  223 + } finally {
  224 + uni.removeStorageSync(tempKey);
  225 + }
  226 + }
  227 +});
  228 +
  229 +onReady(() => {
  230 + if (workOrderFormRef.value) {
  231 + workOrderFormRef.value.setRules(workOrderFormRules)
  232 + }
  233 + console.log('工单表单规则初始化完成')
  234 +})
  235 +
  236 +onShow(() => {
  237 + // 初始化工单名称列表
  238 + orderNameList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('work_name'))
  239 +})
  240 +
  241 +// ========== 核心方法 ==========
  242 +const echoOrderData = (orderItem) => {
  243 + // 回显基础字段
  244 + workOrderForm.workLocation = orderItem.lonLatAddress || orderItem.roadName || '';
  245 + workOrderForm.orderName = orderItem.orderName || '';
  246 + workOrderForm.problemDesc = orderItem.remark || '';
  247 + workOrderForm.lat = orderItem.lat || 0;
  248 + workOrderForm.lon = orderItem.lon || 0;
  249 +
  250 + // 回显图片
  251 + if (orderItem.problemsImgs && Array.isArray(orderItem.problemsImgs) && orderItem.problemsImgs.length > 0) {
  252 + const imgList = orderItem.problemsImgs.map((imgUrl, index) => ({
  253 + url: imgUrl,
  254 + name: `renew_img_${index}`,
  255 + status: 'success'
  256 + }));
  257 + problemImgs.imgList.value = imgList;
  258 + problemImgs.rawImgList.value = imgList;
  259 + }
  260 +};
  261 +
  262 +// ========== 通用弹窗方法 ==========
  263 +const handleActionSheetOpen = (type) => {
  264 + const configMap = {
  265 + orderName: {
  266 + title: '请选择工单名称',
  267 + list: orderNameList.value
  268 + }
  269 + }
  270 +
  271 + currentActionSheetData.type = type
  272 + currentActionSheetData.title = configMap[type].title
  273 + currentActionSheetData.list = configMap[type].list
  274 + showActionSheet.value = true
  275 +}
  276 +
  277 +const handleActionSheetClose = () => {
  278 + showActionSheet.value = false
  279 + currentActionSheetData.type = ''
  280 + currentActionSheetData.list = []
  281 + currentActionSheetData.title = ''
  282 +}
  283 +
  284 +const handleActionSheetSelect = (e) => {
  285 + const { type } = currentActionSheetData
  286 + switch (type) {
  287 + case 'orderName':
  288 + workOrderForm.orderName = e.name
  289 + workOrderFormRef.value?.validateField('orderName')
  290 + break
  291 + }
  292 + showActionSheet.value = false
  293 +}
  294 +
  295 +const navigateBack = () => {
  296 + uni.reLaunch({
  297 + url: '/pages-sub/problem/work-order-manage/index',
  298 + fail: () => {
  299 + uni.navigateBack({ delta: 2 });
  300 + }
  301 + });
  302 +}
  303 +
  304 +// 选择工单位置
  305 +const chooseWorkLocation = () => {
  306 + uni.chooseLocation({
  307 + success: async (res) => {
  308 + workOrderForm.workLocation = res.name
  309 + workOrderForm.lat = res.latitude
  310 + workOrderForm.lon = res.longitude
  311 +
  312 + workOrderFormRef.value?.validateField('workLocation')
  313 + },
  314 + fail: (err) => {
  315 + console.error('选择位置失败:', err)
  316 + uni.showToast({ title: '选择位置失败:' + err.errMsg, icon: 'none' })
  317 + }
  318 + })
  319 +}
  320 +
  321 +// 隐藏键盘
  322 +const hideKeyboard = () => {
  323 + uni.hideKeyboard()
  324 +}
  325 +
  326 +// 提交工单
  327 +const submitWorkOrder = async () => {
  328 + try {
  329 + await workOrderFormRef.value.validate()
  330 +
  331 + const commonSubmitData = {
  332 + problemsImgs: problemImgs.getSuccessImgUrls(),
  333 + remark: workOrderForm.problemDesc.trim(),
  334 + latLonType: 2,
  335 + lat: workOrderForm.lat,
  336 + lon: workOrderForm.lon,
  337 + lonLatAddress: workOrderForm.workLocation,
  338 + orderName: workOrderForm.orderName,
  339 + sourceId: 1
  340 + }
  341 +
  342 + uni.showLoading({ title: '提交中...' })
  343 + let res
  344 +
  345 + if (isRenew.value) {
  346 + const renewSubmitData = {
  347 + workerDataId: renewOrderData.value.id,
  348 + taskKey: renewOrderData.value.taskKey,
  349 + taskId: renewOrderData.value.taskId,
  350 + operateType: nextStepMap[renewOrderData.value.taskKey]?.operateTypeRenew || '',
  351 + agree: 0,
  352 + reason: '重新提交工单',
  353 + ...commonSubmitData
  354 + }
  355 + res = await regionmgrUniversalApproval(renewSubmitData)
  356 + } else {
  357 + res = await regionmgrWorkorderCreate(commonSubmitData)
  358 + }
  359 +
  360 + uni.hideLoading()
  361 + uni.showToast({
  362 + title: isRenew.value ? '重新提交成功' : '工单提交成功',
  363 + icon: 'success',
  364 + duration: 1000
  365 + })
  366 +
  367 + setTimeout(() => {
  368 + uni.reLaunch({
  369 + url: '/pages-sub/problem/regional-order-manage/index'
  370 + })
  371 + }, 1000)
  372 + } catch (error) {
  373 + uni.hideLoading()
  374 +
  375 + if (!Array.isArray(error)) {
  376 + console.error(isRenew.value ? '工单重新提交失败:' : '工单提交失败:', error)
  377 + uni.showToast({
  378 + title: isRenew.value ? error.msg : error.msg,
  379 + icon: 'none',
  380 + duration: 2000
  381 + })
  382 + }
  383 + }
  384 +}
  385 +</script>
  386 +
  387 +<style lang="scss" scoped>
  388 +// 全局页面样式
  389 +.page-container {
  390 + min-height: 100vh;
  391 +}
  392 +
  393 +// 工单表单内容容器
  394 +.work-order-form-content {
  395 + background: #fff;
  396 +}
  397 +
  398 +
  399 +</style>
0 400 \ No newline at end of file
... ...
pages-sub/problem/regional-order-manage/index.vue
... ... @@ -178,7 +178,7 @@
178 178 <!-- </up-button>-->
179 179 <!-- </view>-->
180 180  
181   - <view class="fixed-bottom-btn-wrap">
  181 + <view class="fixed-bottom-btn-wrap" v-if="isInspector">
182 182 <up-button type="primary" size="large" @click="handleAddOrder">
183 183 新增工单
184 184 </up-button>
... ... @@ -315,8 +315,11 @@ const paging = ref(null);
315 315 const orderList = ref([]);
316 316 // 角色控制(巡查员显示新增按钮)
317 317 const isInspector = computed(() => {
  318 + // patrol_global 全域巡查员
  319 + // regional_manager 大区经理
318 320 // 增加可选链,避免用户信息不存在报错
319   - return userStore.userInfo?.roles?.includes('yl_inspector') || false;
  321 + // return userStore.userInfo?.roles?.includes('yl_inspector') || false;
  322 + return true
320 323 });
321 324 // 回退弹窗相关
322 325 const rejectModalShow = ref(false); // 回退modal显示开关
... ... @@ -586,9 +589,19 @@ const confirmReject = async () =&gt; {
586 589  
587 590 // 新增工单
588 591 const handleAddOrder = () => {
589   - uni.navigateTo({
590   - url: '/pages-sub/problem/regional-order-manage/add-order'
591   - });
  592 + // patrol_global 全域巡查员
  593 + // regional_manager 大区经理
  594 + console.log(userStore.userInfo?.roles.includes('patrol_global'))
  595 + if(userStore.userInfo?.roles.includes('patrol_global')){
  596 + uni.navigateTo({
  597 + url: '/pages-sub/problem/regional-order-manage/add-patrol-order'
  598 + });
  599 + }
  600 + if(userStore.userInfo?.roles.includes('regional_manager')){
  601 + uni.navigateTo({
  602 + url: '/pages-sub/problem/wregional-order-manage/add-order'
  603 + });
  604 + }
592 605 };
593 606  
594 607 // 验收弹窗 - 取消按钮(清空状态)
... ...
pages-sub/problem/regional-order-manage/order-detail.vue
... ... @@ -364,7 +364,8 @@ import {
364 364 getDoneTaskDetail,
365 365 getTodoTaskDetail,
366 366 getApprovalDetail,
367   - universalApproval
  367 + regionmgrUniversalApproval
  368 +
368 369 } from '@/api/work-order-manage/work-order-manage';
369 370 import {nextStepMap, buzStatusMap, calculateFormatTimeDiff} from '@/common/utils/common'
370 371 // 引入图片上传组合式函数
... ... @@ -669,7 +670,7 @@ const confirmReject = async () =&gt; {
669 670 "reason": rejectReasonTrim
670 671 };
671 672 // 调用回退工单接口
672   - const res = await universalApproval(requestData);
  673 + const res = await regionmgrUniversalApproval(requestData);
673 674 uni.showToast({title: '回退成功', icon: 'success', duration: 1000});
674 675  
675 676 rejectModalShow.value = false;
... ... @@ -750,7 +751,7 @@ const handleProcess = async (item: any) =&gt; {
750 751 "reason": '结束工单'
751 752 };
752 753 // 调用回退工单接口
753   - const res = await universalApproval(requestData);
  754 + const res = await regionmgrUniversalApproval(requestData);
754 755 uni.showToast({title: '结束成功', icon: 'success', duration: 1000});
755 756 // 重新获取工单详情,刷新页面
756 757 await DetailQuery(taskId.value);
... ... @@ -811,7 +812,7 @@ const handleAcceptModalConfirm = async () =&gt; {
811 812 "agree": acceptRadioValue.value
812 813 }
813 814 }
814   - const acceptRes = await universalApproval(postData);
  815 + const acceptRes = await regionmgrUniversalApproval(postData);
815 816 // 4. 操作成功处理
816 817  
817 818 handleAcceptModalCancel(); // 清空状态
... ...
pages-sub/problem/work-order-manage/add-order.vue
... ... @@ -331,7 +331,6 @@ onLoad((options) =&gt; {
331 331 }
332 332  
333 333 // 判断是否为重新提交状态
334   - console.log('434')
335 334 console.log(options)
336 335 if (options.isRenew == 1 && options.tempKey) {
337 336 isRenew.value = true;
... ...
pages-sub/problem/work-order-manage/index.vue
... ... @@ -309,6 +309,9 @@ const orderList = ref([]);
309 309 // 角色控制(巡查员显示新增按钮)
310 310 const isInspector = computed(() => {
311 311 // 增加可选链,避免用户信息不存在报错
  312 + // patrol_global 全域巡查员
  313 + // regional_manager 大区经理
  314 +
312 315 return userStore.userInfo?.roles?.includes('yl_inspector') || false;
313 316 });
314 317 // 回退弹窗相关
... ...
pages.json
... ... @@ -160,6 +160,11 @@
160 160 },
161 161  
162 162 {
  163 + "path": "regional-order-manage/add-patrol-order",
  164 + "style": { "navigationBarTitleText": "待派单" }
  165 + },
  166 +
  167 + {
163 168 "path": "regional-order-manage/order-detail",
164 169 "style": { "navigationBarTitleText": "工单详情" }
165 170 }
... ...