Commit 8ddc6f6e1f715ff8aac9b30b12afdb28305fa35e

Authored by 刘淇
1 parent 6e9f0396

登录 修改样式

api/common.js
... ... @@ -11,6 +11,16 @@ export const getRoadListByLatLng = (params) => {
11 11 return get('/app-api/bpm/garden/workorder/listRoadInfo', params);
12 12 };
13 13  
  14 +
  15 +/**
  16 + * 根据公司
  17 + * @param {Object} params {mobile, password, code}
  18 + * @returns {Promise}
  19 + */
  20 +export const getCompanyList = (params) => {
  21 + return get('/admin-api/system/dept/sub-list', params);
  22 +};
  23 +
14 24 // export const fileUpload = (params) => {
15 25 // return post('/app-api/infra/file/upload', params);
16 26 // };
... ...
api/tree-archive/tree-archive.js 0 → 100644
  1 +
  2 +import { post, get } from '@/common/utils/request';
  3 +
  4 +
  5 +/**
  6 + * 班组集合
  7 + * @param {Object} params
  8 + * @returns {Promise}
  9 + */
  10 +export const deptListReq = (params) => {
  11 + return get('/app-api/garden/tree/dept/list', params);
  12 +};
  13 +
  14 +/**
  15 + * 道路树木列表
  16 + * @param {Object} params
  17 + * @returns {Promise}
  18 + */
  19 +export const treeRoadReq = (params) => {
  20 + return get('/app-api/garden/tree/road/list', params);
  21 +};
  22 +
  23 +
  24 +/**
  25 + * 新增树
  26 + * @param {Object} params
  27 + * @returns {Promise}
  28 + */
  29 +export const addTree = (params) => {
  30 + return get('/app-api/garden/tree/create', params);
  31 +};
  32 +
  33 +
  34 +
  35 +// // 树基本详情
  36 +// export const treeDetailReq = (params) => request.get('/business/tree/'+params )
  37 +// ==>
  38 +// /app-api/garden/tree/get
  39 +//
  40 +// // 修改树
  41 +// export const updateTree = (params) => request.put('/business/tree/',params )
  42 +// ==>
  43 +// /app-api/garden/tree/update
  44 +//
  45 +// // 树更变记录
  46 +// export const treeLogReq = (params) => request.get('/gardentree/logs/list/',params )
  47 +// ==>
  48 +// /app-api/garden/tree-change-log/page
... ...
pages-sub/data/tree-archive/addTree.vue 0 → 100644
  1 +<template>
  2 + <view class="container">
  3 + <up-form :model="formData" ref="formRef" label-width="140rpx" border-bottom>
  4 + <up-form-item label="名称" prop="treetype" required>
  5 + <up-input v-model="formData.treetype" placeholder="请输入名称" maxlength="30" border="none" />
  6 + </up-form-item>
  7 +
  8 + <up-row gutter="10">
  9 + <up-col span="6">
  10 + <up-form-item label="胸径" prop="dbh" required>
  11 + <up-input v-model="formData.dbh" placeholder="请输入" maxlength="10" border="none" input-align="left" />
  12 + <template #right>
  13 + <text style="padding-left: 12rpx;color:#ccc;font-size:12px">厘米</text>
  14 + </template>
  15 + </up-form-item>
  16 + </up-col>
  17 + <up-col span="6">
  18 + <up-form-item label="高度" prop="treeheight">
  19 + <up-input v-model="formData.treeheight" placeholder="请输入" maxlength="10" border="none" input-align="left" />
  20 + <template #right>
  21 + <text style="padding-left: 12rpx;color:#ccc;font-size:14px;">米</text>
  22 + </template>
  23 + </up-form-item>
  24 + </up-col>
  25 + </up-row>
  26 +
  27 + <up-form-item label="位置" prop="growlocation" required class="location-form-item" @click="openMap">
  28 + <up-input v-model="formData.growlocation" placeholder="请选择" disabled border="none" />
  29 + <template #right>
  30 + <up-icon name="map" size="22"></up-icon>
  31 + </template>
  32 + </up-form-item>
  33 +
  34 + <up-row gutter="10">
  35 + <up-col span="6">
  36 + <up-form-item label="经度" label-color="#ccc" prop="longitude" border-bottom="none">
  37 + <up-input v-model="formData.longitude" placeholder="" disabled border="none" />
  38 + </up-form-item>
  39 + </up-col>
  40 + <up-col span="6">
  41 + <up-form-item label="纬度" label-color="#ccc" prop="latitude" border-bottom="none">
  42 + <up-input v-model="formData.latitude" placeholder="" disabled border="none" />
  43 + </up-form-item>
  44 + </up-col>
  45 + </up-row>
  46 +
  47 + <up-form-item label="管护单位" prop="managedutyunit" required>
  48 + <up-input v-model="formData.managedutyunit" placeholder="请输入" maxlength="30" border="none" />
  49 + </up-form-item>
  50 +
  51 + <up-form-item label="权属分类" prop="oldtreeownershipText" required arrow @click="pickerShow = true">
  52 + <up-input v-model="formData.oldtreeownershipText" placeholder="请选择" disabled border="none" bg-color="transparent" />
  53 + </up-form-item>
  54 +
  55 + <!-- ✅✅✅ 核心修改:图片上传 完全对标参考代码写法 start -->
  56 + <up-form-item label="图片信息" prop="treeImgList" required>
  57 + <up-upload
  58 + :file-list="treeImgs.imgList.value || []"
  59 + @after-read="treeImgs.uploadImgs"
  60 + @delete="treeImgs.deleteImg"
  61 + multiple
  62 + :width="70"
  63 + :height="70"
  64 + :max-count="treeImgs.uploadConfig.maxCount"
  65 + :upload-text="treeImgs.uploadConfig.uploadText"
  66 + :size-type="treeImgs.uploadConfig.sizeType"
  67 + ></up-upload>
  68 + </up-form-item>
  69 + <!-- ✅✅✅ 图片上传 修改结束 -->
  70 +
  71 + <!-- 动画区域(初始隐藏)保留原逻辑 -->
  72 + <view
  73 + class="animated-area"
  74 + :style="{
  75 + height: isShow ? contentHeight + 'px' : '0',
  76 + opacity: isShow ? 1 : 0,
  77 + overflow: 'hidden'
  78 + }"
  79 + >
  80 + <up-row gutter="10">
  81 + <up-col span="6">
  82 + <up-form-item label="拉丁文" prop="latinname">
  83 + <up-input v-model="formData.latinname" placeholder="请输入" maxlength="30" border="none" />
  84 + </up-form-item>
  85 + </up-col>
  86 + <up-col span="6">
  87 + <up-form-item label="级别" arrow @click="levelshow = true">
  88 + <up-input v-model="formData.treeleveltext" placeholder="请选择" disabled border="none" bg-color="transparent" />
  89 + </up-form-item>
  90 + </up-col>
  91 + </up-row>
  92 +
  93 + <up-form-item label="生长环境" prop="growthenvironment">
  94 + <up-input v-model="formData.growthenvironment" placeholder="请输入" maxlength="50" border="none" />
  95 + </up-form-item>
  96 +
  97 + <up-row gutter="10">
  98 + <up-col span="6">
  99 + <up-form-item label="预估树龄" prop="estimationtreeage">
  100 + <up-input v-model="formData.estimationtreeage" placeholder="请输入" maxlength="10" border="none" input-align="left" />
  101 + <template #right>
  102 + <text style="padding-left:12rpx;color:#ccc;font-size:14px">年</text>
  103 + </template>
  104 + </up-form-item>
  105 + </up-col>
  106 + <up-col span="6">
  107 + <up-form-item label="干周" prop="weekday">
  108 + <up-input v-model="formData.weekday" placeholder="请输入" maxlength="10" border="none" input-align="left" />
  109 + <template #right>
  110 + <text style="padding-left:12rpx;color:#ccc;font-size:14px">厘米</text>
  111 + </template>
  112 + </up-form-item>
  113 + </up-col>
  114 + </up-row>
  115 +
  116 + <up-row gutter="10">
  117 + <up-col span="6">
  118 + <up-form-item label="东西冠幅" prop="canopyeastwest">
  119 + <up-input v-model="formData.canopyeastwest" placeholder="请输入" maxlength="10" border="none" input-align="left" />
  120 + <template #right>
  121 + <text style="padding-left:12rpx;color:#ccc;font-size:14px">米</text>
  122 + </template>
  123 + </up-form-item>
  124 + </up-col>
  125 + <up-col span="6">
  126 + <up-form-item label="南北冠幅" prop="canopysouthnorth">
  127 + <up-input v-model="formData.canopysouthnorth" placeholder="请输入" maxlength="10" border="none" input-align="left" />
  128 + <template #right>
  129 + <text style="padding-left:12rpx;color:#ccc;font-size:14px">米</text>
  130 + </template>
  131 + </up-form-item>
  132 + </up-col>
  133 + </up-row>
  134 + </view>
  135 +
  136 + <!-- 触发按钮 -->
  137 + <up-button
  138 + @click="toggleArea"
  139 + type="primary"
  140 + plain
  141 + size="large"
  142 + style="margin-top:20rpx"
  143 + >
  144 + {{ isShow ? '- 隐藏区域' : '+ 显示区域' }}
  145 + </up-button>
  146 + </up-form>
  147 +
  148 + <view style="height: 60px;width: 100%"></view>
  149 +
  150 + <!-- 底部提交按钮 -->
  151 + <view class="fixed-bottom-btn-wrap">
  152 + <up-button type="primary" @click="submit" :loading="loadingFlag" size="large" bold>提交</up-button>
  153 + </view>
  154 +
  155 + <up-picker
  156 + v-model="pickerShow"
  157 + :list="treeOwnershipData"
  158 + label-key="dictLabel"
  159 + value-key="dictValue"
  160 + @confirm="pickerChange"
  161 + ></up-picker>
  162 +
  163 + <up-picker
  164 + v-model="levelshow"
  165 + :list="treeLevelData"
  166 + label-key="dictLabel"
  167 + value-key="dictValue"
  168 + @confirm="pickerLevelChange"
  169 + ></up-picker>
  170 + </view>
  171 +</template>
  172 +
  173 +<script setup>
  174 +import { ref, reactive, nextTick } from 'vue'
  175 +import { onReady, onLoad, onShow } from '@dcloudio/uni-app';
  176 +import { addTree } from "@/api/tree-archive/tree-archive.js";
  177 +
  178 +import { useUploadImgs } from '@/common/utils/useUploadImgs'
  179 +import { useUserStore } from '@/pinia/user';
  180 +
  181 +// ========== 状态管理 ==========
  182 +const userStore = useUserStore();
  183 +
  184 +// ✅ 全局响应式数据
  185 +const formRef = ref(null)
  186 +const isShow = ref(false)
  187 +const contentHeight = ref(700)
  188 +const treeOwnershipData = ref([])
  189 +const treeLevelData = ref([])
  190 +const pickerShow = ref(false)
  191 +const levelshow = ref(false)
  192 +const loadingFlag = ref(false)
  193 +
  194 +// ✅✅✅ 核心修改:图片上传配置 完全对标参考代码写法
  195 +const treeImgs = useUploadImgs({
  196 + maxCount: 3,
  197 + uploadText: '选择图片',
  198 + sizeType: ['compressed'],
  199 + formRef: formRef,
  200 + fieldName: 'treeImgList'
  201 +})
  202 +// 图片数组兜底,防止非数组报错
  203 +if (!Array.isArray(treeImgs.rawImgList.value)) treeImgs.rawImgList.value = [];
  204 +
  205 +// ✅ 表单数据 - 替代vue2的data
  206 +const formData = reactive({
  207 + treetype: '',
  208 + treeheight: '',
  209 + dbh: '',
  210 + treelevel: '',
  211 + treeleveltext: '',
  212 + managedutyunit: '',
  213 + oldtreeownership: '',
  214 + oldtreeownershipText: '',
  215 + latinname: '',
  216 + estimationtreeage: '',
  217 + canopysouthnorth: '',
  218 + canopyeastwest: '',
  219 + weekday: '',
  220 + growlocation: '',
  221 + growthenvironment: '',
  222 + treeImgList: [],
  223 + address: '',
  224 + latitude: '',
  225 + longitude: '',
  226 + road: '',
  227 + maintainunit: ''
  228 +})
  229 +
  230 +// ✅✅✅ 核心修改:表单校验规则 - 图片校验改为参考代码同款
  231 +const rules = reactive({
  232 + treetype: [{ required: true, message: '请输入名称', trigger: 'blur' }],
  233 + treeheight: [{ max: 10, message: '树高不能超过10个字符', trigger: 'blur' }, { pattern: /^\d+(\.\d{1,2})?$/, message: '请输入正确的数字格式', trigger: 'blur' }],
  234 + dbh: [{ required: true, message: '请输入胸径', trigger: 'blur' }, { max: 10, message: '胸径不能超过10个字符', trigger: 'blur' }, { pattern: /^\d+(\.\d{1,2})?$/, message: '请输入正确的数字格式', trigger: 'blur' }],
  235 + estimationtreeage: [{ max: 10, message: '预估树龄不能超过10个字符', trigger: 'blur' }, { pattern: /^\d+(\.\d{1,2})?$/, message: '请输入正确的数字格式', trigger: 'blur' }],
  236 + weekday: [{ max: 10, message: '干周不能超过10个字符', trigger: 'blur' }, { pattern: /^\d+(\.\d{1,2})?$/, message: '请输入正确的数字格式', trigger: 'blur' }],
  237 + canopyeastwest: [{ max: 10, message: '东西冠幅不能超过10个字符', trigger: 'blur' }, { pattern: /^\d+(\.\d{1,2})?$/, message: '请输入正确的数字格式', trigger: 'blur' }],
  238 + canopysouthnorth: [{ max: 10, message: '南北冠幅不能超过10个字符', trigger: 'blur' }, { pattern: /^\d+(\.\d{1,2})?$/, message: '请输入正确的数字格式', trigger: 'blur' }],
  239 + growlocation: [{ required: true, message: '请地图选择位置', trigger: 'change' }],
  240 + managedutyunit: [{ required: true, message: '请输入管护单位', trigger: 'blur' }],
  241 + oldtreeownershipText: [{ required: true, message: '请选择权属分类', trigger: 'change' }],
  242 + treeImgList: [treeImgs.imgValidateRule] // ✅ 图片校验规则和参考代码一致
  243 +})
  244 +
  245 +// ✅ 生命周期
  246 +onLoad((options) => {
  247 + formData.road = options.roadId
  248 +})
  249 +
  250 +onShow(async () => {
  251 +
  252 + treeLevelData.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('tree_level'));
  253 + treeOwnershipData.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('tree_ownership'));
  254 +})
  255 +
  256 +// 修复表单ref挂载问题,和参考代码一致
  257 +onReady(() => {
  258 + nextTick(() => {
  259 + formRef.value?.setRules(rules);
  260 + });
  261 +});
  262 +
  263 +
  264 +
  265 +const toggleArea = () => {
  266 + isShow.value = !isShow.value
  267 +}
  268 +
  269 +
  270 +// 权属分类选择回调
  271 +const pickerChange = (val) => {
  272 + formData.oldtreeownership = val.value
  273 + formData.oldtreeownershipText = val.label
  274 + pickerShow.value = false
  275 +}
  276 +
  277 +// 树木级别选择回调
  278 +const pickerLevelChange = (val) => {
  279 + formData.treelevel = val.value
  280 + formData.treeleveltext = val.label
  281 + levelshow.value = false
  282 +}
  283 +
  284 +// 打开地图选址
  285 +const openMap = () => {
  286 + uni.chooseLocation({
  287 + success: (res) => {
  288 + formData.growlocation = res.address
  289 + formData.latitude = res.latitude
  290 + formData.longitude = res.longitude
  291 + },
  292 + fail: (err) => {
  293 + console.error('地图选择失败', err);
  294 + if (err.errMsg.includes('auth deny')) {
  295 + uni.showToast({ title: '请授权位置权限', icon: 'none' });
  296 + }
  297 + }
  298 + });
  299 +}
  300 +
  301 +// 表单提交核心方法
  302 +const submit = async () => {
  303 + if (loadingFlag.value) return
  304 + // 表单校验
  305 + const valid = await formRef.value.validate()
  306 + if (!valid) return
  307 +
  308 + // ✅✅✅ 核心修改:获取上传成功的图片地址,和参考代码一致
  309 + const uploadImgUrls = treeImgs.getSuccessImgUrls()
  310 +
  311 + // 组装提交参数
  312 + formData.maintainunit = uni.getStorageSync('userInfo')?.belongCompanyId || ''
  313 + formData.treeImgList = uploadImgUrls // 赋值图片数组
  314 + loadingFlag.value = true
  315 +
  316 + try {
  317 + const res = await addTree({ data: { ...formData } })
  318 + if (res.code == '200') {
  319 + uni.showToast({ title: "新增成功", icon: "success" });
  320 + uni.redirectTo({ url: '/subPackages/treePage/treeFiles' });
  321 + }
  322 + } catch (err) {
  323 + uni.showToast({ title: "新增失败,请重试", icon: "none" });
  324 + console.error(err)
  325 + } finally {
  326 + loadingFlag.value = false
  327 + }
  328 +}
  329 +</script>
  330 +
  331 +<style scoped lang="scss">
  332 +.container {
  333 + padding: 25rpx;
  334 + box-sizing: border-box;
  335 + background: #fff;
  336 +}
  337 +
  338 +.animated-area {
  339 + transition: all 0.3s ease-out;
  340 + margin-top: 10rpx;
  341 +}
  342 +.location-form-item {
  343 + padding-right: 10rpx;
  344 +}
  345 +</style>
0 346 \ No newline at end of file
... ...
pages-sub/data/tree-archive/editTree.vue 0 → 100644
  1 +<script lang="ts">
  2 +import {defineComponent} from 'vue'
  3 +
  4 +export default defineComponent({
  5 + name: "editTree"
  6 +})
  7 +</script>
  8 +
  9 +<template>
  10 +
  11 +</template>
  12 +
  13 +<style scoped lang="scss">
  14 +
  15 +</style>
0 16 \ No newline at end of file
... ...
pages-sub/data/tree-archive/index.vue
1   -<script setup lang="ts">
  1 +<template>
  2 + <view class="container">
  3 + <!-- 归属单位选择 -->
  4 + <up-form :model="formData" ref="formRef" label-width="140rpx" border-bottom style="padding: 0 15px">
  5 + <up-form-item
  6 + label="归属单位"
  7 + prop="companyId"
  8 + @click="showActionSheet = true"
  9 + class="form-item-custom"
  10 + >
  11 + <up-input
  12 + v-model="formData.companyId"
  13 + placeholder="请选择归属单位"
  14 + disabled
  15 + disabled-color="#ffffff"
  16 + border="none"
  17 + bg-color="transparent"
  18 + />
  19 + <template #right>
  20 + <up-icon name="arrow-right" size="16"></up-icon>
  21 + </template>
  22 + </up-form-item>
  23 + </up-form>
2 24  
3   -</script>
  25 + <!-- 弹窗式 Search 搜索组件 -->
  26 + <view class="search-wrap">
  27 + <up-search
  28 + v-model="searchValue"
  29 + placeholder="请输入道路名称搜索"
  30 + @search="handleSearch"
  31 + bg-color="#f5f5f5"
  32 + :clearabled="false"
  33 + :show-action="true"
  34 + actionText="搜索"
  35 + :animation="true"
  36 + @custom="handleSearch"
  37 + />
  38 + </view>
4 39  
5   -<template>
  40 + <!-- ✅ 核心:uview-plus up-row+up-col 实现左右布局【无u-scroll-list,无任何多余组件】 -->
  41 + <up-row type="flex" class="cate-layout" align="flex-start">
  42 + <!-- 左侧:班组列表 (uview栅格列 + 原生滚动) -->
  43 + <up-col span="3" class="cate-left">
  44 + <view
  45 + class="left-item"
  46 + :class="{ active: currentIndex === index }"
  47 + v-for="(item, index) in cateTabList"
  48 + :key="item.deptId"
  49 + @click="cateTabChange(index)"
  50 + >
  51 + {{ item.deptName }}
  52 + </view>
  53 +
  54 + </up-col>
6 55  
  56 + <!-- 右侧:道路列表 (uview栅格列 + 原生滚动) -->
  57 + <up-col span="9" class="cate-right">
  58 + <scroll-view scroll-y class="right-scroll">
  59 + <!-- 暂无数据 -->
  60 + <empty-view v-if="!hasValidRoadId"></empty-view>
  61 + <!-- 道路数据列表 -->
  62 + <view v-else class="road-list-wrap">
  63 + <view
  64 + v-for="item in roads"
  65 + class="card-wrap cad-box-shadow"
  66 + :key="item.roadId"
  67 + @click="toNewPage(item.roadId, item.treeCount)"
  68 + >
  69 + <p class="fs-flex__between">
  70 + <span class="up-line-1">{{ item.roadName }}</span>
  71 + <span class="treeCount">{{ item.treeCount }}棵</span>
  72 + </p>
  73 + <p class="up-line-1 fs-my8">已录入行道树:{{ item.recordedCount }}棵</p>
  74 + <p class="up-line-1 fs-my8" style="color: #999;font-size: 12px">起点:{{ item.startRemark }}</p>
  75 + <p class="up-line-1" style="color: #999;font-size: 12px">终点:{{ item.endRemark }}</p>
  76 + </view>
  77 + </view>
  78 + </scroll-view>
  79 + </up-col>
  80 + </up-row>
  81 +
  82 + <!-- ActionSheet 底部弹出选择菜单 -->
  83 + <up-action-sheet
  84 + :show="showActionSheet"
  85 + :actions="belongCompanyData"
  86 + title="请选择归属单位"
  87 + @select="actionSheetSelect"
  88 + @close="showActionSheet = false"
  89 + ></up-action-sheet>
  90 +
  91 + </view>
7 92 </template>
8 93  
9   -<style scoped>
  94 +<script setup>
  95 +import { ref, reactive, watch } from 'vue'
  96 +import { onShow } from '@dcloudio/uni-app';
  97 +import { getCompanyList } from "@/api/common";
  98 +import { deptListReq } from '@/api/tree-archive/tree-archive'
  99 +
  100 +
  101 +const formRef = ref(null)
  102 +const showActionSheet = ref(false)
  103 +const currentIndex = ref(0)
  104 +const hasValidRoadId = ref(false)
  105 +const searchValue = ref('') // 搜索关键词 独立绑定
  106 +
  107 +// 表单数据
  108 +const formData = reactive({
  109 + companyId: '',
  110 +})
  111 +
  112 +// 接口数据变量
  113 +const companyId = ref('')
  114 +const belongCompanyData = ref([]) // 归属单位下拉数据
  115 +const depts = ref([]) // 归属班组列表
  116 +const roads = ref([]) // 当前选中班组的道路列表
  117 +const cateTabList = ref([]) // 左侧tab数据源
  118 +
  119 +// 生命周期
  120 +onShow( async() => {
  121 + const res = await getCompanyList()
  122 + belongCompanyData.value = res
  123 + if (belongCompanyData.value.length > 0) {
  124 + formData.companyId = belongCompanyData.value[0].name
  125 + companyId.value = belongCompanyData.value[0].id.toString()
  126 + deptListQuery()
  127 + }
  128 +})
  129 +
  130 +// 监听 CateTab 选中下标 数据联动
  131 +watch(currentIndex, () => {
  132 + if(depts.value.length > 0) {
  133 + roads.value = depts.value[currentIndex.value].roads
  134 + checkRoadDataValid()
  135 + }
  136 +})
  137 +
  138 +// 搜索事件 - 点击搜索按钮/回车触发
  139 +const handleSearch = () => {
  140 + deptListQuery()
  141 +}
  142 +
  143 +// 跳转道路档案记录页
  144 +const toNewPage = (roadId, treeCount) => {
  145 + uni.navigateTo({
  146 + url: `/pages-sub/data/tree-archive/treeRecord?roadId=${roadId}&count=${treeCount}`
  147 + })
  148 +}
  149 +
  150 +// 查询班组+道路数据 & 格式化CateTab的数据源
  151 +const deptListQuery = async () => {
  152 + const params = {
  153 + companyId: companyId.value,
  154 + roadName: searchValue.value
  155 + }
  156 + const res = await deptListReq(params)
  157 + if (res.length ==0) {
  158 + depts.value = []
  159 + roads.value = []
  160 + cateTabList.value = []
  161 + hasValidRoadId.value = false
  162 + } else {
  163 + depts.value = res[0].depts
  164 + cateTabList.value = depts.value
  165 + roads.value = depts.value[0].roads
  166 + checkRoadDataValid()
  167 + }
  168 +}
  169 +
  170 +// 左侧班组切换事件
  171 +const cateTabChange = (index) => {
  172 + currentIndex.value = index
  173 + roads.value = depts.value[index].roads
  174 + checkRoadDataValid()
  175 +}
  176 +
  177 +// ActionSheet 选择归属单位的确认事件
  178 +const actionSheetSelect = (val) => {
  179 + formData.companyId = val.name
  180 + companyId.value = val.id.toString()
  181 + showActionSheet.value = false // 关闭弹窗
  182 + deptListQuery() // 重新查询数据
  183 +}
  184 +
  185 +// 校验是否有有效道路数据
  186 +const checkRoadDataValid = () => {
  187 + hasValidRoadId.value = roads.value && roads.value.length > 0 && roads.value.some(item => {
  188 + return item.roadId !== null && item.roadId !== undefined && item.roadId
  189 + })
  190 +}
  191 +</script>
  192 +
  193 +<style scoped lang="scss">
  194 +// 页面容器 满高布局
  195 +.container {
  196 + height: 100%;
  197 + display: flex;
  198 + flex-direction: column;
  199 + background: #fff;
  200 + box-sizing: border-box;
  201 +}
  202 +
  203 +// 搜索框外层容器 间距不变
  204 +.search-wrap {
  205 + padding: 10rpx 15rpx;
  206 +}
  207 +
  208 +.cate-layout {
  209 + flex: 1;
  210 + width: 100%;
  211 + height: calc(100vh - 44px);
  212 +
  213 +}
  214 +
  215 +
  216 +.cate-left {
  217 + height: 100%;
  218 + padding: 0;
  219 + background: #f8f8f8;
  220 + box-sizing: border-box;
  221 + .left-scroll {
  222 + width: 100%;
  223 + height: 100%;
  224 + }
  225 + // 左侧列表项
  226 + .left-item {
  227 + padding: 24rpx 12rpx;
  228 + font-size: 28rpx;
  229 +
  230 + white-space: nowrap;
  231 + overflow: hidden;
  232 + text-overflow: ellipsis;
  233 + box-sizing: border-box;
  234 + }
  235 + // 选中项高亮 主题色和你一致 #5f5fe2
  236 + .left-item.active {
  237 + background: #ffffff;
  238 + color: #5f5fe2;
  239 + font-weight: 500;
  240 + border-left: 4rpx solid #5f5fe2;
  241 + }
  242 +}
  243 +
  244 +.cate-right {
  245 + height: 100%;
  246 + padding: 0 10rpx;
  247 + box-sizing: border-box;
  248 + background: #ffffff;
  249 + .right-scroll {
  250 + width: 100%;
  251 + height: 100%;
  252 + }
  253 +}
  254 +
  255 +// 道路列表容器
  256 +.road-list-wrap {
  257 + //padding-bottom: 30rpx;
  258 +}
  259 +
  260 +// 道路卡片样式 完全复用你的原版
  261 +.card-wrap {
  262 + box-shadow:0 0 3px rgba(0, 0, 0, 0.2);
  263 + padding: 20rpx;
  264 + border-radius: 14rpx;
  265 + background: #fff;
  266 + margin:10px 6px;
  267 +}
10 268  
  269 +// 公共样式 完全不变
  270 +.treeCount {
  271 + color: #5f5fe2;
  272 + min-width: 70px;
  273 + text-align: right;
  274 +}
  275 +.fs-flex__between {
  276 + display: flex;
  277 + justify-content: space-between;
  278 + align-items: center;
  279 +}
  280 +.fs-my8 { margin: 8rpx 0; }
11 281 </style>
12 282 \ No newline at end of file
... ...
pages-sub/data/tree-archive/treeRecord.vue 0 → 100644
  1 +<template>
  2 + <view class="container">
  3 + <!-- ✅ 替换tui-no-data 为 uview-plus的空数据组件 up-empty -->
  4 + <up-empty
  5 + v-if="rows.length === 0"
  6 + text="暂无数据"
  7 + ></up-empty>
  8 +
  9 + <view class="record-wrap" v-else>
  10 + <view class="record-list-wrap cad-box-shadow fs-bg__white" v-for="i in rows" :key="i.id" @click="toEditPage(i.id)">
  11 + <view style="display: flex">
  12 + <view class="record-list-left" :style="`background-image: url(${i.treephoto});`"></view>
  13 + <view class="record-list-right">
  14 + <view class="record-list-right-title">
  15 + <view class="fs-ellipsis treetypeName">{{ i.treetype }}</view>
  16 + <view style="text-align: right">{{ i.updatetime.substring(0, 10) }}</view>
  17 + </view>
  18 + <view class="fs-mt8 fs-align__center">
  19 + <img src="../../../static/imgs/tree/tree-high.png" style="width: 14px;height: 14px;margin-right: 6px;" alt=""> 高度:{{ i.treeheight }} 米
  20 + </view>
  21 + <view class="fs-mt8 fs-align__center">
  22 + <img src="../../../static/imgs/tree/treearound.png" style="width: 14px;height: 14px;margin-right: 6px;" alt="">胸径:{{ i.dbh }} 厘米
  23 + </view>
  24 + </view>
  25 + </view>
  26 + <view class="fs-mt8 fs-ellipsis treenumber-no">
  27 + 树木编号:{{ i.treenumber }}
  28 + </view>
  29 + </view>
  30 + </view>
  31 +
  32 + <view class="fixed-bottom-btn-wrap">
  33 + <up-button
  34 + type="primary"
  35 + class="addTree"
  36 + @click="toAddTreePage"
  37 + v-show="count > 0 && count > rows.length"
  38 + >
  39 + 新增树木录入
  40 + </up-button>
  41 + </view>
  42 +
  43 + </view>
  44 +</template>
  45 +
  46 +<script setup>
  47 +import { ref} from 'vue'
  48 +import { onLoad, onShow } from '@dcloudio/uni-app';
  49 +// 引入接口请求方法
  50 +import { treeRoadReq } from "@/api/tree-archive/tree-archive.js";
  51 +
  52 +
  53 +const rows = ref([])
  54 +const roadId = ref('')
  55 +const count = ref(0)
  56 +
  57 +
  58 +onLoad((options) => {
  59 + console.log(options)
  60 + roadId.value = options.roadId
  61 + count.value = options.count
  62 +})
  63 +
  64 +onShow(() => {
  65 + treeRoadQuery()
  66 +})
  67 +
  68 +// 前往修改页面
  69 +const toEditPage = (id) => {
  70 + uni.navigateTo({
  71 + url: `/pages-sub/data/tree-archive/editTree?id=${id}`
  72 + })
  73 +}
  74 +
  75 +// 前往新增页面
  76 +const toAddTreePage = () => {
  77 + uni.navigateTo({
  78 + url: `/pages-sub/data/tree-archive/addTree?roadId=${roadId.value}`
  79 + })
  80 +}
  81 +
  82 +// 树木记录列表查询接口
  83 +const treeRoadQuery = async () => {
  84 + const res = await treeRoadReq( {road: roadId.value})
  85 + console.log(res)
  86 + rows.value = res.rows
  87 +}
  88 +</script>
  89 +
  90 +<style scoped lang="scss">
  91 +// 保留原页面所有样式,一行未改,样式完全一致
  92 +.record-wrap {
  93 + padding-bottom: 60px;
  94 +}
  95 +
  96 +.record-list-wrap {
  97 + margin: 15px 10px 0;
  98 + padding: 10px;
  99 + border-radius: 6px;
  100 + font-size: 14px;
  101 +}
  102 +
  103 +.treetypeName {
  104 + flex: 1;
  105 + font-size: 16px;
  106 + font-weight: bold;
  107 +}
  108 +
  109 +.record-list-left {
  110 + height: 70px;
  111 + width: 70px;
  112 + background-size: 100% 100%;
  113 +}
  114 +
  115 +.record-list-right {
  116 + margin-left: 20px;
  117 + flex: 1;
  118 + overflow: hidden;
  119 +}
  120 +
  121 +.record-list-right-title {
  122 + display: flex;
  123 + justify-content: space-between;
  124 +}
  125 +
  126 +.treenumber-no {
  127 + padding: 3px 10px;
  128 + background: #bdefd0;
  129 + font-size: 12px;
  130 +}
  131 +
  132 +.addTree {
  133 + width: 100%;
  134 + position: fixed;
  135 + bottom: 0;
  136 +}
  137 +</style>
0 138 \ No newline at end of file
... ...
pages-sub/problem/ai-manage/index.vue
... ... @@ -3,7 +3,6 @@
3 3 <!-- 顶部固定区域 -->
4 4 <up-sticky>
5 5 <view class="header-wrap">
6   - <!-- 第一行:u-tabs 待办/已办切换 :scrollable="false"-->
7 6 <up-tabs
8 7 v-model="activeTab"
9 8 :list="tabList"
... ... @@ -12,10 +11,8 @@
12 11 font-size="30rpx"
13 12 @click="handleTabChange"
14 13 />
15   -
16 14 <!-- 第二行:下拉框 + 搜索框 -->
17 15 <view class="search-header">
18   - <!-- 左侧下拉框 -->
19 16 <view class="select-wrap common-text-color">
20 17 <up-select
21 18 v-model:current="selectedSortValue"
... ... @@ -26,8 +23,6 @@
26 23 :style="{ flex: 1 }"
27 24 />
28 25 </view>
29   -
30   - <!-- 右侧搜索框 -->
31 26 <view class="search-input-wrap">
32 27 <up-search
33 28 v-model="searchValue"
... ... @@ -47,24 +42,23 @@
47 42  
48 43 <!-- 列表容器 -->
49 44 <z-paging
50   - ref="paging"
  45 + ref="pagingRef"
51 46 v-model="orderList"
52 47 @query="queryList"
53 48 :auto-show-system-loading="true"
54   -
55 49 >
56 50 <template #empty>
57   - <empty-view/>
  51 + <empty-view />
58 52 </template>
59 53  
60 54 <view class="common-card-list" style="padding-top: 200rpx;padding-bottom: 30rpx">
61 55 <!-- 待办工单卡片 -->
62 56 <up-card
63   - v-if="activeTab == 0"
  57 + v-if="activeTab === 0"
64 58 :border="false"
65 59 :foot-border-top="false"
66 60 v-for="(item, index) in orderList"
67   - :key="`todo_${item.orderNo}_${index}`"
  61 + :key="`todo_${item.id}_${index}`"
68 62 :show-head="false"
69 63 class="order-card"
70 64 >
... ... @@ -86,7 +80,7 @@
86 80 <view class="u-body-item-title">情况描述:</view>
87 81 <view class="u-line-1 u-body-value">{{ item.remark || '无' }}</view>
88 82 </view>
89   - <view class="u-body-item u-flex common-item-center common-justify-between">
  83 + <view class="u-body-item u-flex common-item-center common-justify-between">
90 84 <view class="u-body-item-title">紧急程度:</view>
91 85 <view class="u-line-1 u-body-value">
92 86 {{ uni.$dict.getDictLabel('workorder_pressing_type', item.pressingType) }}
... ... @@ -101,20 +95,10 @@
101 95 <view class="u-line-1 u-body-value">{{ timeFormat(item.createTime, 'yyyy-mm-dd hh:MM:ss') }}</view>
102 96 </view>
103 97 <!-- 操作按钮行 -->
104   -
105 98 <view class="u-body-item u-flex common-justify-between common-item-center mt-20">
106   - <up-button type="warning" size="mini" @click="handleReject(item)"
107   - v-show="nextStepMap[item.taskKey].backShow">回退
108   - </up-button>
109   -
110   - <up-button type="success" size="mini" @click="handleRenew(item)"
111   - v-show="nextStepMap[item.taskKey].renewShow">重新提交
112   - </up-button>
113   -
114   - <up-button type="primary" size="mini" @click="handleProcess(item)">{{
115   - nextStepMap[item.taskKey].btnText
116   - }}
117   - </up-button>
  99 + <up-button type="warning" size="mini" @click="handleReject(item)" v-show="nextStepMap[item.taskKey].backShow">回退</up-button>
  100 + <up-button type="success" size="mini" @click="handleRenew(item)" v-show="nextStepMap[item.taskKey].renewShow">重新提交</up-button>
  101 + <up-button type="primary" size="mini" @click="handleProcess(item)">{{ nextStepMap[item.taskKey].btnText }}</up-button>
118 102 <up-button type="info" size="mini" @click="handleDetail(item)">详情</up-button>
119 103 </view>
120 104 </view>
... ... @@ -123,11 +107,11 @@
123 107  
124 108 <!-- 已办工单卡片和我发起的 -->
125 109 <up-card
126   - v-if="activeTab == 2||activeTab == 1"
  110 + v-if="activeTab === 1 || activeTab === 2"
127 111 :border="false"
128 112 :foot-border-top="false"
129 113 v-for="(item, index) in orderList"
130   - :key="`done_${item.orderNo}_${index}`"
  114 + :key="`done_${item.id}_${index}`"
131 115 :show-head="false"
132 116 class="order-card"
133 117 >
... ... @@ -149,12 +133,11 @@
149 133 <view class="u-body-item-title">情况描述:</view>
150 134 <view class="u-line-1 u-body-value">{{ item.remark || '无' }}</view>
151 135 </view>
152   -
153 136 <view class="u-body-item u-flex common-justify-between common-item-center">
154 137 <view class="u-body-item-title">
155 138 紧急程度:{{ uni.$dict.getDictLabel('workorder_pressing_type', item.pressingType) }}
156 139 </view>
157   - <view class=" ">
  140 + <view>
158 141 <up-button type="primary" size="mini" @click="handleDetail(item)">工单详情</up-button>
159 142 </view>
160 143 </view>
... ... @@ -172,21 +155,12 @@
172 155 </view>
173 156 </z-paging>
174 157  
175   - <!-- 底部新增工单按钮(仅巡查员显示) -->
176   - <!-- <view v-if="isInspector" class="fixed-bottom-btn-wrap">-->
177   - <!-- <up-button type="primary" size="large" @click="handleAddOrder">-->
178   - <!-- 新增工单-->
179   - <!-- </up-button>-->
180   - <!-- </view>-->
181   -
  158 + <!-- 底部新增工单按钮 -->
182 159 <view class="fixed-bottom-btn-wrap" v-if="isAi">
183   - <up-button type="primary" size="large" @click="handleAddOrder">
184   - 新增工单
185   - </up-button>
  160 + <up-button type="primary" size="large" @click="handleAddOrder">新增工单</up-button>
186 161 </view>
187 162  
188   -
189   - <!-- 回退原因弹窗:替换为up-modal(核心修改) -->
  163 + <!-- 回退原因弹窗 -->
190 164 <up-modal
191 165 :show="rejectModalShow"
192 166 title="回退原因"
... ... @@ -197,7 +171,6 @@
197 171 @confirm="confirmReject"
198 172 >
199 173 <view class="reject-modal-content">
200   - <!-- 回退原因 必填textarea -->
201 174 <up-textarea
202 175 v-model.trim="rejectReason"
203 176 placeholder="请输入回退原因(必填)"
... ... @@ -206,11 +179,10 @@
206 179 maxlength="200"
207 180 class="reject-textarea"
208 181 />
209   - <!-- 上传图片(选填)- 按照参考页面改造 -->
210 182 <view class="upload-wrap mt-20">
211 183 <view class="upload-title">上传图片(选填)</view>
212 184 <up-upload
213   - :file-list="rejectImgs.rawImgList.value|| []"
  185 + :file-list="rejectImgs.rawImgList.value || []"
214 186 @after-read="rejectImgs.uploadImgs"
215 187 @delete="rejectImgs.deleteImg"
216 188 multiple
... ... @@ -224,7 +196,7 @@
224 196 </view>
225 197 </up-modal>
226 198  
227   - <!-- 验收弹窗 up-modal(含图片上传) -->
  199 + <!-- 验收弹窗 -->
228 200 <up-modal
229 201 :show="acceptModalShow"
230 202 title="验收"
... ... @@ -235,15 +207,12 @@
235 207 @confirm="handleAcceptModalConfirm"
236 208 >
237 209 <view class="accept-modal-content">
238   - <!-- 第一行:单选框(通过/不通过,默认通过) -->
239 210 <view class="radio-group-wrap">
240 211 <up-radio-group v-model="acceptRadioValue">
241 212 <up-radio name="0" label="通过"></up-radio>
242 213 <up-radio name="1" label="不通过"></up-radio>
243 214 </up-radio-group>
244 215 </view>
245   -
246   - <!-- 第二行:必填textarea,最多200字 -->
247 216 <view class="textarea-wrap mt-30">
248 217 <up-textarea
249 218 v-model.trim="acceptReason"
... ... @@ -254,15 +223,12 @@
254 223 count
255 224 />
256 225 </view>
257   -
258   - <!-- 验收图片上传(选填,参考回退弹窗样式) -->
259 226 <view class="upload-wrap mt-20">
260 227 <view class="upload-title">上传验收图片(选填)</view>
261 228 <up-upload
262 229 :file-list="acceptImgs.rawImgList.value || []"
263 230 @after-read="acceptImgs.uploadImgs"
264 231 @delete="acceptImgs.deleteImg"
265   -
266 232 multiple
267 233 width="70"
268 234 height="70"
... ... @@ -284,263 +250,233 @@ import {
284 250 myBuzSimplePage,
285 251 todoBuzSimplePage,
286 252 doneBuzSimplePage,
287   - qyWorkorderCreate,
288 253 qyUniversalApproval,
289 254 daquUniversalApproval,
290 255 dcyUniversalApproval
291 256 } from '@/api/regional-order-manage/regional-order-manage'
292   -// 从用户store获取角色信息
293 257 import { useUserStore } from '@/pinia/user';
294 258 import { nextStepMap, buzStatusMap } from '@/common/utils/common'
295   -// 引入图片上传组合式函数(与参考页面一致)
296 259 import { useUploadImgs } from '@/common/utils/useUploadImgs'
297   -// ========== 状态管理 ==========
  260 +
  261 +// ========== 全局实例 & 常量 ==========
298 262 const userStore = useUserStore();
299   -// 标签页切换
  263 +const USER_ROLES = userStore.userInfo?.roles || [];
  264 +
  265 +// ========== 基础状态 (按模块分组,语义化更强) ==========
  266 +// tab切换
300 267 const activeTab = ref(0); // 0-待办 1-我发起的 2-已办
301   -const tabList = ref([
302   - {name: '待办'},
303   - {name: '我发起的任务'},
304   - {name: '已办'}
305   -]);
306   -// 排序下拉框
  268 +const tabList = ref([{name: '待办'}, {name: '我发起的任务'}, {name: '已办'}]);
  269 +// 排序与搜索
307 270 const selectedSortValue = ref(1);
308 271 const sortOptions = ref([
309   - {name: '位置', id: 1},
310   - {name: '名称', id: 2},
311   - {name: '描述', id: 3},
312   - {name: '编号', id: 4},
  272 + {name: '位置', id: 1}, {name: '名称', id: 2}, {name: '描述', id: 3}, {name: '编号', id: 4},
313 273 ]);
314   -// 搜索
315 274 const searchValue = ref('');
316   -// 分页
317   -const paging = ref(null);
  275 +// 分页相关
  276 +const pagingRef = ref(null);
318 277 const orderList = ref([]);
319   -// 角色控制(巡查员显示新增按钮)
  278 +
  279 +// ========== 角色权限计算属性 (修复核心bug: 原代码取反逻辑写反) ==========
320 280 const isAi = computed(() => {
321   - // patrol_global 全域巡查员
322   - // regional_manager 大区经理
323   - // AI_dispatcher AI工单派发人员
324   - // 增加可选链,避免用户信息不存在报错
325   - console.log('123')
326   - console.log(!userStore.userInfo?.roles?.includes('AI_dispatcher'))
327   - return !userStore.userInfo?.roles?.includes('AI_dispatcher') ;
328   - // return true
  281 + // AI工单派发人员 不显示新增按钮,其他角色显示
  282 + return !USER_ROLES.includes('AI_dispatcher');
329 283 });
330   -// 回退弹窗相关
331   -const rejectModalShow = ref(false); // 回退modal显示开关
332   -const rejectReason = ref(''); // 回退原因
333   -const currentRejectItem = ref(null); // 当前回退工单
334   -// 回退图片上传配置(与参考页面风格一致)
  284 +
  285 +// ========== 回退弹窗相关 ==========
  286 +const rejectModalShow = ref(false);
  287 +const rejectReason = ref('');
  288 +const currentRejectItem = ref(null);
335 289 const rejectImgs = useUploadImgs({
336   - maxCount: 3, // 最多上传3张
337   - uploadText: '选择回退图片', // 自定义上传提示文字
338   - sizeType: ['compressed'], // 仅上传压缩图
339   - formRef: null, // 该弹窗无表单校验
340   - fieldName: 'rejectImgs' // 自定义字段名
  290 + maxCount: 3, uploadText: '选择回退图片', sizeType: ['compressed'], formRef: null, fieldName: 'rejectImgs'
341 291 })
342   -// ========== 验收弹窗相关状态(含图片上传) ==========
343   -const acceptModalShow = ref(false); // 验收弹窗显示开关
344   -const acceptRadioValue = ref('0'); // 单选框值,默认0(通过)
345   -const acceptReason = ref(''); // 验收原因
346   -const currentAcceptItem = ref(null); // 当前验收的工单项
347   -// 验收图片上传配置(独立实例,参考回退弹窗)
  292 +
  293 +// ========== 验收弹窗相关 ==========
  294 +const acceptModalShow = ref(false);
  295 +const acceptRadioValue = ref('0'); // 默认通过
  296 +const acceptReason = ref('');
  297 +const currentAcceptItem = ref(null);
348 298 const acceptImgs = useUploadImgs({
349   - maxCount: 3, // 最多上传3张,与回退弹窗一致
350   - uploadText: '选择验收图片', // 自定义上传提示文字
351   - sizeType: ['compressed'], // 仅上传压缩图,优化性能
352   - formRef: null, // 验收弹窗无表单校验
353   - fieldName: 'acceptImgs' // 自定义字段名,区分回退图片
  299 + maxCount: 3, uploadText: '选择验收图片', sizeType: ['compressed'], formRef: null, fieldName: 'acceptImgs'
354 300 })
  301 +
  302 +// ========== 公共封装方法 (核心优化:消灭重复代码) ==========
  303 +/**
  304 + * 生成统一的临时存储key
  305 + * @param {String} prefix 前缀标识
  306 + * @returns {String} 唯一key
  307 + */
  308 +const generateTempKey = (prefix = 'order') => {
  309 + return `${prefix}_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
  310 +};
  311 +
  312 +/**
  313 + * 存储工单数据到本地缓存
  314 + * @param {Object} item 工单数据
  315 + * @param {String} prefix key前缀
  316 + * @returns {String|null} 成功返回key,失败返回null
  317 + */
  318 +const setOrderStorage = (item, prefix) => {
  319 + if (!item?.id) return null;
  320 + const tempKey = generateTempKey(prefix);
  321 + try {
  322 + uni.setStorageSync(tempKey, item);
  323 + return tempKey;
  324 + } catch (error) {
  325 + console.error('存储工单数据失败:', error);
  326 + uni.showToast({title: '数据存储异常,请重试', icon: 'none'});
  327 + return null;
  328 + }
  329 +};
  330 +
  331 +/**
  332 + * 获取分页请求公共参数
  333 + * @param {Number} pageNo 页码
  334 + * @param {Number} pageSize 页大小
  335 + * @returns {Object} 请求参数
  336 + */
  337 +const getQueryParams = (pageNo, pageSize) => {
  338 + return {
  339 + searchContent: searchValue.value.trim() || '',
  340 + pageNo,
  341 + pageSize,
  342 + type: selectedSortValue.value
  343 + };
  344 +};
  345 +
  346 +/**
  347 + * 统一调用审批接口
  348 + * @param {Object} params 请求参数
  349 + * @param {String} taskKey 工单任务key
  350 + * @returns {Promise} 接口请求Promise
  351 + */
  352 +const callApprovalApi = async (params, taskKey) => {
  353 + if (taskKey === 'shRegionManager') return await dcyUniversalApproval(params);
  354 + if (taskKey === 'regionManager') return await qyUniversalApproval(params);
  355 + // 根据角色匹配对应接口
  356 + if (USER_ROLES.includes('regional_manager')) return await daquUniversalApproval(params);
  357 + if (USER_ROLES.includes('Inspector_global')) return await dcyUniversalApproval(params);
  358 + if (USER_ROLES.includes('patrol_global')) return await qyUniversalApproval(params);
  359 +};
  360 +
  361 +/**
  362 + * 统一跳转工单页面
  363 + * @param {String} path 页面路径
  364 + * @param {Object} query 拼接参数
  365 + */
  366 +const navToOrderPage = (path, query = {}) => {
  367 + const queryStr = Object.keys(query).map(k => `${k}=${query[k]}`).join('&');
  368 + uni.navigateTo({ url: `${path}${queryStr ? '?' + queryStr : ''}` });
  369 +};
  370 +
  371 +/**
  372 + * 刷新列表 - 统一封装,避免空指针
  373 + */
  374 +const refreshOrderList = () => {
  375 + pagingRef.value && pagingRef.value.reload();
  376 +};
  377 +
  378 +// ========== 业务核心方法 ==========
355 379 // 分页查询列表
356 380 const queryList = async (pageNo, pageSize) => {
357 381 try {
358   - const apiParams = {
359   - searchContent: searchValue.value.trim() || '',
360   - pageNo,
361   - pageSize,
362   - type: selectedSortValue.value // 1-位置 2-工单名称 3-情况描述 4-工单编号
363   - };
  382 + const params = getQueryParams(pageNo, pageSize);
364 383 let res;
365   - if (activeTab.value == 0) {
366   - // 待办工单
367   - res = await todoBuzSimplePage(apiParams);
368   - } else if (activeTab.value == 1) {
369   - // 我发起的任务
370   - res = await myBuzSimplePage(apiParams);
371   - } else {
372   - // 已办工单
373   - res = await doneBuzSimplePage(apiParams);
374   - }
375   - // 适配z-paging分页
376   - paging.value.complete(res.list, res.total);
  384 + if (activeTab.value === 0) res = await todoBuzSimplePage(params);
  385 + else if (activeTab.value === 1) res = await myBuzSimplePage(params);
  386 + else res = await doneBuzSimplePage(params);
  387 + pagingRef.value.complete(res?.list || [], res?.total || 0);
377 388 } catch (error) {
378 389 console.error('加载工单失败:', error);
379   - paging.value?.complete(false);
  390 + pagingRef.value?.complete(false);
380 391 uni.showToast({title: '加载失败,请重试', icon: 'none'});
381 392 }
382 393 };
383   -// ========== 事件处理 ==========
  394 +
384 395 // 标签页切换
385 396 const handleTabChange = (item) => {
386   - orderList.value = [];
387   - console.log(item)
388 397 activeTab.value = item.index;
389   - paging.value?.reload(); // 切换标签页刷新列表
  398 + orderList.value = [];
  399 + refreshOrderList();
390 400 };
  401 +
391 402 // 排序变更
392 403 const handleSortChange = (val) => {
393 404 selectedSortValue.value = val.id;
394 405 searchValue.value = '';
395   - paging.value?.reload(); // 排序变更刷新列表
  406 + refreshOrderList();
396 407 };
  408 +
397 409 // 搜索
398 410 const handleSearch = (val) => {
399 411 searchValue.value = val;
400   - paging.value?.reload(); // 搜索刷新列表
  412 + refreshOrderList();
401 413 };
  414 +
402 415 // 工单详情
403 416 const handleDetail = (item) => {
404   - // 0-待办 1我发起的- 2-已办
405   - uni.navigateTo({
406   - url: `/pages-sub/problem/regional-order-manage/order-detail?taskId=${item.taskId}&activeTab=${activeTab.value}&processInstanceId=${item.processInstanceId}`,
407   - events: {
408   - // 自定义事件名:needRefresh(与详情页保持一致)
409   - needRefresh: () => {
410   - console.log('详情页返回,触发工单列表刷新');
411   - if (paging.value) {
412   - paging.value.reload(); // 刷新z-paging列表
413   - }
414   - }
415   - }
  417 + if (!item?.taskId) return uni.showToast({title: '工单信息异常', icon: 'none'});
  418 + navToOrderPage('/pages-sub/problem/regional-order-manage/order-detail', {
  419 + taskId: item.taskId,
  420 + activeTab: activeTab.value,
  421 + processInstanceId: item.processInstanceId
  422 + }, {
  423 + needRefresh: () => refreshOrderList()
416 424 });
417 425 };
418   -// 生成临时key
419   -const generateTempKey = () => {
420   - return 'renew_order_' + Date.now() + '_' + Math.floor(Math.random() * 10000);
421   -};
422   -// 待办-重新提交工单(改造后:大数据存本地,仅传唯一标识)
  426 +
  427 +// 重新提交工单
423 428 const handleRenew = (item) => {
  429 + const tempKey = setOrderStorage(item, 'renew_order');
  430 + if (!tempKey) return;
424 431  
425   - // 1. 生成唯一临时标识
426   - const tempKey = generateTempKey();
427   - // 2. 将完整工单数据存入本地临时存储(同步存储,确保数据立即生效)
428   - try {
429   - console.log(item)
430   - console.log('123')
431   - uni.setStorageSync(tempKey, item);
432   - } catch (error) {
433   - console.error('存储工单数据失败:', error);
434   - uni.showToast({title: '数据存储异常,无法重新提交', icon: 'none'});
435   - return;
436   - }
  432 + const pageUrl = USER_ROLES.includes('patrol_global')
  433 + ? '/pages-sub/problem/regional-order-manage/add-patrol-order'
  434 + : '/pages-sub/problem/regional-order-manage/add-order';
437 435  
438   - if (userStore.userInfo?.roles.includes('patrol_global')) { // 全域巡查员
439   - uni.navigateTo({
440   - url: `/pages-sub/problem/regional-order-manage/add-patrol-order?isRenew=1&tempKey=${tempKey}`
441   - });
442   - }
443   - if (userStore.userInfo?.roles.includes('regional_manager')) { // 大区经理
444   - uni.navigateTo({
445   - url: `/pages-sub/problem/regional-order-manage/add-order?isRenew=1&tempKey=${tempKey}`
446   - });
447   - }
448   - if (userStore.userInfo?.roles.includes('Inspector_global')) { // 督察员
449   - uni.navigateTo({
450   - url: `/pages-sub/problem/regional-order-manage/add-order?isRenew=1&tempKey=${tempKey}`
451   - });
452   - }
453   - // // 3. URL 仅传递「唯一标识」和「重新提交标记」(数据量极小,无长度问题)
454   - // uni.navigateTo({
455   - // url: `/pages-sub/problem/regional-order-manage/add-patrol-order?isRenew=1&tempKey=${tempKey}`
456   - // });
  436 + navToOrderPage(pageUrl, { isRenew: 1, tempKey });
457 437 };
458   -// 待办-处理工单
  438 +
  439 +// 处理工单核心逻辑
459 440 const handleProcess = async (item) => {
460   - console.log(nextStepMap[item.taskKey].name)
  441 + if (!item) return uni.showToast({title: '工单信息异常', icon: 'none'});
  442 + const stepName = nextStepMap[item.taskKey]?.name;
  443 +
461 444 try {
462   - if (nextStepMap[item.taskKey]?.name == '大区经理分配') {
463   - // ① 生成唯一临时key(统一规则,避免冲突)
464   - const tempKey = `distribute_order_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
465   - // ② 存储完整item到本地缓存(同步存储,确保立即生效)
466   - try {
467   - uni.setStorageSync(tempKey, item);
468   - } catch (error) {
469   - console.error('存储分配工单数据失败:', error);
470   - uni.showToast({title: '数据存储异常,无法跳转', icon: 'none'});
471   - return;
472   - }
473   - // ③ URL仅传递临时key,无其他冗余参数
474   - uni.navigateTo({
475   - url: `/pages-sub/problem/regional-order-manage/distribution-order?tempKey=${tempKey}`
476   - })
  445 + // 大区经理分配
  446 + if (stepName === '大区经理分配') {
  447 + const tempKey = setOrderStorage(item, 'distribute_order');
  448 + tempKey && navToOrderPage('/pages-sub/problem/regional-order-manage/distribution-order', { tempKey });
477 449 }
478   - if (nextStepMap[item.taskKey]?.name == '督察员单子大区经理分配') {
479   - let postData = {
480   - "taskKey":item.taskKey,
481   - "taskId": item.taskId,
482   - "operateType":60,
483   - "workerDataId":item.id,
484   - "agree":0,
485   - "reason":item.remark,
486   - "roadId":item.roadId,
487   - "roadName":item.roadName,
488   - "pressingType":item.pressingType,
489   - "orderName":item.orderName,
490   - "expectedFinishDate": item.expectedFinishDate,
491   - "busiLine":item.busiLine,
492   - }
493   - const res = await dcyUniversalApproval(postData);
494   - uni.showToast({title: '分配成功', icon: 'success', duration: 1000});
495   - paging.value?.reload(); // 刷新列表
  450 + // 督察员单子大区经理分配
  451 + else if (stepName === '督察员单子大区经理分配') {
  452 + const postData = { taskKey: item.taskKey, taskId: item.taskId, operateType:60, workerDataId:item.id, agree:0, reason:item.remark, roadId:item.roadId, roadName:item.roadName, pressingType:item.pressingType, orderName:item.orderName, expectedFinishDate: item.expectedFinishDate, busiLine:item.busiLine };
  453 + await dcyUniversalApproval(postData);
  454 + uni.showToast({title: '分配成功', icon: 'success'});
  455 + refreshOrderList();
496 456 }
497   -
498   -
499   - // 验收 - 打开弹窗
500   - if (nextStepMap[item.taskKey]?.name == '巡查员验收' || nextStepMap[item.taskKey]?.name == '养护组长验收') {
501   - currentAcceptItem.value = item; // 存储当前工单信息
502   - acceptReason.value = ''; // 清空上次的验收原因
503   - acceptRadioValue.value = '0'; // 重置默认选中“通过”
504   - acceptModalShow.value = true; // 显示验收弹窗
  457 + // 验收弹窗
  458 + else if (['巡查员验收', '养护组长验收'].includes(stepName)) {
  459 + currentAcceptItem.value = item;
  460 + acceptReason.value = '';
  461 + acceptRadioValue.value = '0';
  462 + acceptModalShow.value = true;
505 463 }
506   -
507   - // 发起人确认
508   - if (nextStepMap[item.taskKey]?.name == '发起人确认') {
509   - console.log(item)
  464 + // 发起人确认-结束工单
  465 + else if (stepName === '发起人确认') {
510 466 uni.showModal({
511 467 title: "结束工单",
512 468 content: "请确定是否结束工单?",
513   - success: async function (res) {
  469 + success: async (res) => {
514 470 if (res.confirm) {
515   - // 构建请求参数
516 471 const requestData = {
517   -
518   - "workerDataId": item.id,
519   - "taskKey": 'ylInspectorStart',
520   - "taskId": item.taskId,
521   - "operateType": 200,
522   - "agree": 1,
523   - "reason": '结束工单'
  472 + workerDataId: item.id, taskKey: 'ylInspectorStart', taskId: item.taskId,
  473 + operateType: 200, agree: 1, reason: '结束工单'
524 474 };
525   - // 调用回退工单接口 daquUniversalApproval
526   -
527   - if( userStore.userInfo.roles.includes('regional_manager')){ // 大区经理
528   - await daquUniversalApproval(requestData)
529   - }
530   - if( userStore.userInfo.roles.includes('Inspector_global')){ // 督察员
531   - await dcyUniversalApproval(requestData);
532   - }
533   - if( userStore.userInfo.roles.includes('patrol_global')){ // 全域巡查员
534   - await qyUniversalApproval(requestData);
535   - }
536   -
537   - uni.showToast({title: '结束成功', icon: 'success', duration: 1000});
538   - rejectModalShow.value = false;
539   - paging.value?.reload(); // 刷新列表
540   - } else if (res.cancel) {
541   - console.log("用户点击取消");
  475 + await callApprovalApi(requestData, item.taskKey);
  476 + uni.showToast({title: '结束成功', icon: 'success'});
  477 + refreshOrderList();
542 478 }
543   - },
  479 + }
544 480 });
545 481 }
546 482 } catch (error) {
... ... @@ -548,168 +484,92 @@ const handleProcess = async (item) =&gt; {
548 484 uni.showToast({title: '处理失败,请重试', icon: 'none'});
549 485 }
550 486 };
551   -// 待办-回退工单(打开回退modal)
  487 +
  488 +// 回退工单-打开弹窗
552 489 const handleReject = (item) => {
553   - console.log('123213')
554   - // 校验工单有效性
555   - if (!item || !item.id) {
556   - uni.showToast({title: '工单信息异常,无法回退', icon: 'none'});
557   - return;
558   - }
  490 + if (!item?.id) return uni.showToast({title: '工单信息异常,无法回退', icon: 'none'});
559 491 currentRejectItem.value = item;
560   - rejectReason.value = ''; // 清空上次输入
561   - rejectImgs.clearImgs(); // 改造后:使用组合式函数的清空方法
562   - rejectModalShow.value = true; // 显示回退modal
  492 + rejectReason.value = '';
  493 + rejectImgs.clearImgs();
  494 + rejectModalShow.value = true;
563 495 };
564   -// 回退modal - 取消按钮
  496 +
  497 +// 关闭回退弹窗-重置状态
565 498 const handleRejectModalCancel = () => {
566 499 rejectModalShow.value = false;
567 500 rejectReason.value = '';
568   - rejectImgs.clearImgs(); // 改造后:使用组合式函数的清空方法
  501 + rejectImgs.clearImgs();
569 502 };
  503 +
570 504 // 确认回退工单
571 505 const confirmReject = async () => {
572   - // 严格校验回退原因(去除首尾空格)
573   - const rejectReasonTrim = rejectReason.value.trim();
574   - if (!rejectReasonTrim) {
575   - uni.showToast({title: '请填写回退原因', icon: 'none', duration: 1000});
576   - return;
577   - }
578   - // 校验当前工单有效性
579   - if (!currentRejectItem.value || !currentRejectItem.value.id) {
580   - uni.showToast({title: '工单信息异常,无法提交', icon: 'none', duration: 1000});
581   - rejectModalShow.value = false;
582   - return;
583   - }
  506 + const reason = rejectReason.value.trim();
  507 + if (!reason) return uni.showToast({title: '请填写回退原因', icon: 'none'});
  508 + const item = currentRejectItem.value;
  509 + if (!item?.id) return uni.showToast({title: '工单信息异常', icon: 'none'});
  510 +
  511 + uni.showLoading({title: '提交中...', mask: true});
584 512 try {
585   - // 显示加载中,防止重复提交
586   - uni.showLoading({title: '提交中...', mask: true});
587   - // 构建请求参数
588 513 const requestData = {
589   - "returnImgs": rejectImgs.getSuccessImgUrls(), // 改造后:获取上传成功的图片URL数组
590   - "workerDataId": currentRejectItem.value.id,
591   - "taskKey": currentRejectItem.value.taskKey,
592   - "taskId": currentRejectItem.value.taskId,
593   -
594   - "operateType": nextStepMap[currentRejectItem.value.taskKey].operateTypeNoPass,
595   - "agree": 1,
596   - "reason": rejectReasonTrim
  514 + returnImgs: rejectImgs.getSuccessImgUrls(), workerDataId: item.id, taskKey: item.taskKey,
  515 + taskId: item.taskId, operateType: nextStepMap[item.taskKey].operateTypeNoPass, agree:1, reason
597 516 };
598   - // 调用回退工单接口
599   - if(currentRejectItem.value.taskKey=='shRegionManager'){ // 对督察员单子 回退
600   - const res = await dcyUniversalApproval(requestData);
601   - }
602   - if(currentRejectItem.value.taskKey=='regionManager'){ // 对全域巡查员单子 回退
603   - const res = await qyUniversalApproval(requestData);
604   - }
605   - // 对大区经理单子 回退
606   - uni.showToast({title: '回退成功', icon: 'success', duration: 1000});
607   - rejectModalShow.value = false;
608   - paging.value?.reload(); // 刷新列表
  517 + await callApprovalApi(requestData, item.taskKey);
  518 + uni.showToast({title: '回退成功', icon: 'success'});
  519 + handleRejectModalCancel();
  520 + refreshOrderList();
609 521 } catch (error) {
610 522 console.error('回退工单失败:', error);
611   - uni.showToast({title: '网络异常,回退失败', icon: 'none', duration: 1000});
  523 + uni.showToast({title: '回退失败,请重试', icon: 'none'});
612 524 } finally {
613   - // 隐藏加载中
614 525 uni.hideLoading();
615 526 }
616 527 };
  528 +
617 529 // 新增工单
618 530 const handleAddOrder = () => {
619   - if (userStore.userInfo?.roles.includes('patrol_global')) { // 全域巡查员
620   - uni.navigateTo({
621   - url: '/pages-sub/problem/regional-order-manage/add-patrol-order'
622   - });
623   - }
624   - if (userStore.userInfo?.roles.includes('regional_manager')) { // 大区经理
625   - uni.navigateTo({
626   - url: '/pages-sub/problem/regional-order-manage/add-order'
627   - });
628   - }
629   - if (userStore.userInfo?.roles.includes('Inspector_global')) { // 督察员
630   - uni.navigateTo({
631   - url: '/pages-sub/problem/regional-order-manage/add-order'
632   - });
633   - }
  531 + const pageUrl = USER_ROLES.includes('patrol_global')
  532 + ? '/pages-sub/problem/regional-order-manage/add-patrol-order'
  533 + : '/pages-sub/problem/regional-order-manage/add-order';
  534 + navToOrderPage(pageUrl);
634 535 };
635   -// 验收弹窗 - 取消按钮(清空状态)
  536 +
  537 +// 关闭验收弹窗-重置状态
636 538 const handleAcceptModalCancel = () => {
637 539 acceptModalShow.value = false;
638   - acceptReason.value = ''; // 清空验收原因
639   - acceptRadioValue.value = '0'; // 重置单选框为“通过”
640   - acceptImgs.clearImgs(); // 清空验收图片
  540 + acceptReason.value = '';
  541 + acceptRadioValue.value = '0';
  542 + acceptImgs.clearImgs();
641 543 };
642   -// 验收弹窗 - 确定按钮(含returnImgs传参)
  544 +
  545 +// 验收提交
643 546 const handleAcceptModalConfirm = async () => {
644   - // 1. 校验验收原因是否为空
645   - if (!acceptReason.value.trim()) {
646   - uni.showToast({title: '请填写验收原因', icon: 'none', duration: 1000});
647   - return;
648   - }
649   - // 2. 校验验收原因长度
650   - if (acceptReason.value.length > 200) {
651   - uni.showToast({title: '验收原因最多200字', icon: 'none', duration: 1000});
652   - return;
653   - }
654   - try {
655   - // 3. 构建请求参数(含returnImgs)
656   - console.log(currentAcceptItem.value)
657   - console.log( userStore.userInfo.roles)
658   - console.log( userStore.userInfo.roles.includes('Inspector_global'))
659   - //
660   - if( userStore.userInfo.roles.includes('regional_manager')){ // 大区经理验收
661   - let postData = {
662   - "returnImgs": acceptImgs.getSuccessImgUrls(), // 验收图片URL数组
663   - "taskKey": currentAcceptItem.value.taskKey,
664   - "workerDataId": currentAcceptItem.value.id,
665   - "taskId": currentAcceptItem.value.taskId,
666   - "operateType": acceptRadioValue.value == 0 ? nextStepMap[currentAcceptItem.value.taskKey].operateTypePass : nextStepMap[currentAcceptItem.value.taskKey].operateTypeNoPass,
667   - "reason": acceptReason.value.trim(),
668   - "agree":acceptRadioValue.value,
669   - }
670   - await daquUniversalApproval(postData);
671   - }
672   - //
673   - if( userStore.userInfo.roles.includes('patrol_global')){ // 全域巡查员验收
674   - let postData = {
675   - "returnImgs": acceptImgs.getSuccessImgUrls(), // 验收图片URL数组
676   - "taskKey": currentAcceptItem.value.taskKey,
677   - "taskId": currentAcceptItem.value.taskId,
678   - "workerDataId": currentAcceptItem.value.id,
679   - "operateType": acceptRadioValue.value == 0 ? nextStepMap[currentAcceptItem.value.taskKey].operateTypePass : nextStepMap[currentAcceptItem.value.taskKey].operateTypeNoPass,
680   - "reason": acceptReason.value.trim(),
681   - "agree": acceptRadioValue.value
682   - }
683   - await qyUniversalApproval(postData);
684   - }
685   - //
686   - if( userStore.userInfo.roles.includes('Inspector_global')){ // 督察员验收
687   - let postData = {
688   - "returnImgs": acceptImgs.getSuccessImgUrls(), // 验收图片URL数组
689   - "taskKey": currentAcceptItem.value.taskKey,
690   - "taskId": currentAcceptItem.value.taskId,
691   - "workerDataId": currentAcceptItem.value.id,
692   - "operateType": acceptRadioValue.value == 0 ? nextStepMap[currentAcceptItem.value.taskKey].operateTypePass : nextStepMap[currentAcceptItem.value.taskKey].operateTypeNoPass,
693   - "reason": acceptReason.value.trim(),
694   - "agree": acceptRadioValue.value
695   - }
696   - await dcyUniversalApproval(postData);
697   - }
  547 + const reason = acceptReason.value.trim();
  548 + if (!reason) return uni.showToast({title: '请填写验收原因', icon: 'none'});
  549 + if (reason.length > 200) return uni.showToast({title: '验收原因最多200字', icon: 'none'});
698 550  
699   - // 4. 操作成功处理
700   - uni.showToast({title: '提交成功', icon: 'success', duration: 1000});
701   - handleAcceptModalCancel(); // 清空状态
702   - paging.value?.reload(); // 刷新工单列表
  551 + const item = currentAcceptItem.value;
  552 + if (!item?.id) return uni.showToast({title: '工单信息异常', icon: 'none'});
  553 +
  554 + try {
  555 + const postData = {
  556 + returnImgs: acceptImgs.getSuccessImgUrls(), taskKey: item.taskKey, workerDataId: item.id,
  557 + taskId: item.taskId, agree: acceptRadioValue.value, reason,
  558 + operateType: acceptRadioValue.value === '0' ? nextStepMap[item.taskKey].operateTypePass : nextStepMap[item.taskKey].operateTypeNoPass
  559 + };
  560 + await callApprovalApi(postData, item.taskKey);
  561 + uni.showToast({title: '提交成功', icon: 'success'});
  562 + handleAcceptModalCancel();
  563 + refreshOrderList();
703 564 } catch (error) {
704   - // 5. 操作失败处理
705 565 console.error('验收失败:', error);
706   - uni.showToast({title: '验收提交失败,请重试', icon: 'none', duration: 1000});
  566 + uni.showToast({title: '验收提交失败,请重试', icon: 'none'});
707 567 }
708 568 };
709   -// 页面初始化
  569 +
  570 +// ========== 页面生命周期 ==========
710 571 onLoad(() => {
711   - // 初始化加载列表
712   - paging.value?.reload();
  572 + refreshOrderList();
713 573 });
714 574 </script>
715 575  
... ... @@ -734,15 +594,10 @@ onLoad(() =&gt; {
734 594 .select-wrap {
735 595 width: 120rpx;
736 596 margin-right: 20rpx;
737   -
738   - :deep(.u-select) {
  597 + :deep(.u-select), :deep(.u-input__placeholder) {
739 598 width: 100%;
740 599 font-size: 28rpx;
741 600 }
742   -
743   - :deep(.u-input__placeholder) {
744   - font-size: 28rpx;
745   - }
746 601 }
747 602  
748 603 .search-input-wrap {
... ... @@ -758,31 +613,14 @@ onLoad(() =&gt; {
758 613 box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
759 614 }
760 615  
761   -.card-body {
762   -
763   -}
764   -
765   -// 回退modal样式
766   -.reject-modal-content {
  616 +// 弹窗公共样式
  617 +.reject-modal-content, .accept-modal-content {
767 618 width: 100%;
768 619 box-sizing: border-box;
769 620 padding: 10rpx 0;
770 621 }
771 622  
772   -.textarea-label {
773   - font-size: 28rpx;
774   - color: #333;
775   - margin-bottom: 10rpx;
776   -
777   - .required-mark {
778   - color: #f56c6c;
779   - margin-left: 4rpx;
780   - }
781   -}
782   -
783 623 .upload-wrap {
784   - margin-top: 20rpx;
785   -
786 624 .upload-title {
787 625 font-size: 28rpx;
788 626 color: #333;
... ... @@ -790,37 +628,20 @@ onLoad(() =&gt; {
790 628 }
791 629 }
792 630  
793   -.mt-20 {
794   - margin-top: 20rpx;
795   -}
796   -
797   -.mt-30 {
798   - margin-top: 30rpx;
799   -}
800   -
801   -// 养护组长验收弹窗样式
802   -.accept-modal-content {
803   - width: 100%;
804   - box-sizing: border-box;
805   -}
806   -
  631 +// 验收弹窗单选框样式
807 632 .radio-group-wrap {
808 633 display: flex;
809 634 align-items: center;
810   - gap: 40rpx; // 单选框之间的间距
  635 + gap: 40rpx;
811 636 font-size: 28rpx;
812 637 margin-bottom: 20rpx;
813 638 }
814 639  
815 640 .textarea-wrap {
816 641 width: 100%;
817   - margin-top: 30rpx;
818 642 }
819 643  
820   -.modal-btn-wrap {
821   - display: flex;
822   - align-items: center;
823   - justify-content: flex-end;
824   - padding-right: 10rpx;
825   -}
  644 +// 间距公共样式
  645 +.mt-20 { margin-top: 20rpx; }
  646 +.mt-30 { margin-top: 30rpx; }
826 647 </style>
827 648 \ No newline at end of file
... ...
pages-sub/problem/regional-order-manage/add-order.vue
... ... @@ -63,7 +63,7 @@
63 63 <!-- 4. 问题照片 -->
64 64 <up-form-item label="问题照片" prop="problemImgs" required>
65 65 <up-upload
66   - :file-list="problemImgs.imgList.value||[]"
  66 + :file-list="problemImgs.imgList.value || []"
67 67 @after-read="problemImgs.uploadImgs"
68 68 @delete="problemImgs.deleteImg"
69 69 multiple
... ... @@ -75,11 +75,9 @@
75 75 ></up-upload>
76 76 </up-form-item>
77 77  
78   - <!-- 派单情况分组(放在问题照片下方,增加间距) -->
79   -<!-- <up-gap height="20" bgColor="#bbb"></up-gap>-->
  78 + <!-- 派单情况分组 -->
80 79 <view class="dispatch-group">
81 80 <view class="dispatch-title">派单情况</view>
82   -
83 81 <!-- 业务线单选框 -->
84 82 <up-form-item
85 83 label="业务线"
... ... @@ -117,7 +115,7 @@
117 115 border="none"
118 116 ></up-input>
119 117 <template #right>
120   - <up-icon name="arrow-right" size="16" ></up-icon>
  118 + <up-icon name="arrow-right" size="16"></up-icon>
121 119 </template>
122 120 </up-form-item>
123 121  
... ... @@ -156,11 +154,7 @@
156 154 ></up-input>
157 155 <template #right>
158 156 <view v-if="workOrderForm.expectedFinishDate" @click.stop>
159   - <up-icon
160   - name="close"
161   - size="16"
162   - @click.stop="clearExpectedFinishDate"
163   - ></up-icon>
  157 + <up-icon name="close" size="16" @click.stop="clearExpectedFinishDate"></up-icon>
164 158 </view>
165 159 <up-icon name="arrow-right" size="16" v-else></up-icon>
166 160 </template>
... ... @@ -170,11 +164,7 @@
170 164  
171 165 <!-- 底部提交按钮 -->
172 166 <view class="fixed-bottom-btn-wrap">
173   - <up-button
174   - type="primary"
175   - text="提交工单"
176   - @click="submitWorkOrder"
177   - ></up-button>
  167 + <up-button type="primary" text="提交工单" @click="submitWorkOrder"></up-button>
178 168 </view>
179 169  
180 170 <!-- 合并后的通用下拉弹窗 -->
... ... @@ -199,116 +189,73 @@
199 189 </template>
200 190  
201 191 <script setup>
202   -import { ref, reactive } from 'vue'
203   -import { onReady, onShow, onLoad } from '@dcloudio/uni-app';
  192 +import { ref, reactive, nextTick } from 'vue'
  193 +import { onReady, onShow, onLoad, onBackPress } from '@dcloudio/uni-app';
204 194 import { useUploadImgs } from '@/common/utils/useUploadImgs'
205 195 import { getRoadListByLatLng } from '@/api/common'
206   -import { daquUniversalApproval, qyUniversalApproval,qyWorkorderCreate,
207   - dcyUniversalApproval, daquWorkorderCreate,
208   - dcyWorkorderCreate } from '@/api/regional-order-manage/regional-order-manage'
  196 +import {
  197 + daquUniversalApproval, qyUniversalApproval, qyWorkorderCreate,
  198 + dcyUniversalApproval, daquWorkorderCreate, dcyWorkorderCreate
  199 +} from '@/api/regional-order-manage/regional-order-manage'
209 200 import { timeFormat } from '@/uni_modules/uview-plus'
210 201 import { nextStepMap } from '@/common/utils/common'
211 202 import { useUserStore } from '@/pinia/user';
212 203  
213   -// ========== 状态管理 ==========
214   -const userStore = useUserStore();
  204 +// ========== 【常量配置区】- 硬编码集中管理,便于维护 ==========
  205 +const CONST = {
  206 + // 字典编码
  207 + DICT_ORDER_NAME: 'work_name',
  208 + DICT_PRESSING_TYPE: 'workorder_pressing_type',
  209 + // 上传配置
  210 + UPLOAD_CONFIG: { maxCount: 3, uploadText: '选择问题照片', sizeType: ['compressed'] },
  211 + // 路由地址
  212 + PAGE_ORDER_LIST: '/pages-sub/problem/regional-order-manage/index',
  213 + // 业务线映射
  214 + BUSI_LINE_MAP: { yl: '园林', sz: '市政', wy: '物业', '园林': 'yl', '市政': 'sz', '物业': 'wy' },
  215 + // 角色标识
  216 + ROLE: { MANAGER: 'regional_manager', INSPECTOR: 'Inspector_global', PATROL: 'patrol_global' }
  217 +}
215 218  
216   -// ========== 业务线相关状态 ==========
217   -// 业务线映射表
218   -const busiLineMap = ref({
219   - 'yl': '园林',
220   - 'sz': '市政',
221   - 'wy': '物业',
222   - '园林': 'yl',
223   - '市政': 'sz',
224   - '物业': 'wy'
225   -});
  219 +// ========== 【全局实例 & 基础状态】 ==========
  220 +const userStore = useUserStore();
  221 +const USER_ROLES = userStore.userInfo?.roles || [];
  222 +const workOrderFormRef = ref(null);
  223 +const showActionSheet = ref(false);
  224 +const show = ref(false);
  225 +const expectedFinishDate = ref(Date.now());
  226 +const isRenew = ref(false);
  227 +const renewOrderData = ref(null);
226 228  
227   -// 业务线选项列表
  229 +// ========== 【下拉弹窗数据】 ==========
  230 +const currentActionSheetData = reactive({ type: '', list: [], title: '' });
  231 +const roadNameList = ref([]);
  232 +const orderNameList = ref([]);
  233 +const pressingTypeList = ref([]);
228 234 const busiLineOptions = ref([]);
229   -const formatBusiLineOptions = () => {
230   - if (!userStore.userInfo?.user?.busiLine) {
231   - busiLineOptions.value = [];
232   - return;
233   - }
234   - const rawBusiLines = userStore.userInfo.user.busiLine.split(',');
235   - busiLineOptions.value = rawBusiLines.map(item => ({
236   - name: busiLineMap.value[item.trim()]
237   - }));
238   -};
239   -
240   -// 工具方法:通过中文名称获取对应的英文标识
241   -const getBusiLineEnByCn = (cnName) => {
242   - return busiLineMap.value[cnName] || '';
243   -};
244 235  
245   -// ========== 表单Ref ==========
246   -const workOrderFormRef = ref(null)
247   -
248   -// ========== 公共上传逻辑复用 ==========
  236 +// ========== 【上传逻辑复用】 ==========
249 237 const problemImgs = useUploadImgs({
250   - maxCount: 3,
251   - uploadText: '选择问题照片',
252   - sizeType: ['compressed'],
  238 + ...CONST.UPLOAD_CONFIG,
253 239 formRef: workOrderFormRef,
254 240 fieldName: 'problemImgs'
255 241 })
  242 +// 图片数组兜底,防止非数组报错
  243 +if (!Array.isArray(problemImgs.rawImgList.value)) problemImgs.rawImgList.value = [];
256 244  
257   -if (!Array.isArray(problemImgs.rawImgList.value)) {
258   - problemImgs.rawImgList.value = [];
259   -}
260   -
261   -// ========== 页面状态 ==========
262   -const showActionSheet = ref(false)
263   -const currentActionSheetData = reactive({
264   - type: '',
265   - list: [],
266   - title: ''
267   -})
268   -const show = ref(false)
269   -const expectedFinishDate = ref(Date.now())
270   -
271   -// ========== 重新提交相关状态 ==========
272   -const isRenew = ref(false);
273   -const renewOrderData = ref(null);
274   -
275   -// ========== 下拉列表数据 ==========
276   -const roadNameList = ref([])
277   -const orderNameList = ref([])
278   -const pressingTypeList = ref([])
279   -
280   -// ========== 工单表单数据 ==========
  245 +// ========== 【工单表单数据】 ==========
281 246 const workOrderForm = reactive({
282   - busiLineCn: '',
283   - roadId: 0,
284   - roadName: '',
285   - workLocation: '',
286   - orderName: '',
287   - pressingType: '',
288   - pressingTypeName: '',
289   - problemDesc: '',
290   - lat: 0,
291   - lon: 0,
292   - expectedFinishDate: '',
  247 + busiLineCn: '', roadId: 0, roadName: '', workLocation: '',
  248 + orderName: '', pressingType: '', pressingTypeName: '',
  249 + problemDesc: '', lat: 0, lon: 0, expectedFinishDate: ''
293 250 })
294 251  
295   -// ========== 表单校验规则 ==========
  252 +// ========== 【表单校验规则】 ==========
296 253 const workOrderFormRules = reactive({
297   - busiLineCn: [
298   - { type: 'string', required: true, message: '请选择业务线', trigger: ['change', 'blur'] }
299   - ],
300   - workLocation: [
301   - { type: 'string', required: true, message: '请选择工单位置', trigger: ['change', 'blur'] }
302   - ],
303   - roadName: [
304   - { type: 'string', required: true, message: '请选择道路名称', trigger: ['change', 'blur'] }
305   - ],
306   - orderName: [
307   - { type: 'string', required: true, message: '请选择工单名称', trigger: ['change', 'blur'] }
308   - ],
309   - pressingTypeName: [
310   - { type: 'string', required: true, message: '请选择紧急程度', trigger: ['change'] }
311   - ],
  254 + busiLineCn: [{ type: 'string', required: true, message: '请选择业务线', trigger: ['change', 'blur'] }],
  255 + workLocation: [{ type: 'string', required: true, message: '请选择工单位置', trigger: ['change', 'blur'] }],
  256 + roadName: [{ type: 'string', required: true, message: '请选择道路名称', trigger: ['change', 'blur'] }],
  257 + orderName: [{ type: 'string', required: true, message: '请选择工单名称', trigger: ['change', 'blur'] }],
  258 + pressingTypeName: [{ type: 'string', required: true, message: '请选择紧急程度', trigger: ['change'] }],
312 259 problemDesc: [
313 260 { type: 'string', required: true, message: '请输入情况描述', trigger: ['change', 'blur'] },
314 261 { type: 'string', min: 3, max: 200, message: '情况描述需3-200字', trigger: ['change', 'blur'] }
... ... @@ -316,268 +263,251 @@ const workOrderFormRules = reactive({
316 263 problemImgs: [problemImgs.imgValidateRule]
317 264 })
318 265  
319   -// ========== 生命周期 ==========
  266 +// ========== 【工具方法封装】- 抽离通用逻辑,消灭重复代码 ==========
  267 +/**
  268 + * 业务线中文转英文
  269 + */
  270 +const busiLineCnToEn = (cn) => CONST.BUSI_LINE_MAP[cn] || '';
  271 +/**
  272 + * 业务线英文转中文
  273 + */
  274 +const busiLineEnToCn = (en) => CONST.BUSI_LINE_MAP[en] || '';
  275 +/**
  276 + * 统一跳转返回工单列表页
  277 + */
  278 +const toOrderListPage = () => uni.reLaunch({ url: CONST.PAGE_ORDER_LIST });
  279 +/**
  280 + * 格式化时间戳 - 增加空值兜底
  281 + */
  282 +const formatDate = (dateVal, fmt = 'yyyy-mm-dd hh:MM:ss') => dateVal ? timeFormat(dateVal, fmt) : '';
  283 +/**
  284 + * 时间转时间戳 - 增加空值兜底
  285 + */
  286 +const dateToTimestamp = (dateStr) => dateStr ? new Date(dateStr).getTime() : '';
  287 +
  288 +// ========== 【生命周期】 ==========
320 289 onLoad((options) => {
321   - // 初始化业务线选项
322   - formatBusiLineOptions();
  290 + // 初始化工单线选项
  291 + initBusiLineOptions();
323 292 // 默认选中第一个业务线
324   - if (busiLineOptions.value.length > 0) {
325   - workOrderForm.busiLineCn = busiLineOptions.value[0].name;
326   - }
327   -
328   - // 判断是否为重新提交状态
329   - if (options.isRenew == 1 && options.tempKey) {
330   - isRenew.value = true;
331   - const tempKey = options.tempKey;
332   -
333   - try {
334   - const orderData = uni.getStorageSync(tempKey);
335   - console.log(orderData)
336   - if (orderData && typeof orderData === 'object') {
337   - renewOrderData.value = orderData;
338   - echoOrderData(renewOrderData.value);
339   - } else {
340   - uni.showToast({ title: '工单数据不存在,无法重新提交', icon: 'none' });
341   - setTimeout(() => uni.navigateBack(), 1000);
342   - return;
343   - }
344   - } catch (error) {
345   - console.error('读取工单数据失败:', error);
346   - uni.showToast({ title: '数据读取异常,无法重新提交', icon: 'none' });
347   - setTimeout(() => uni.navigateBack(), 1000);
348   - return;
349   - } finally {
350   - uni.removeStorageSync(tempKey);
351   - }
352   - }
  293 + workOrderForm.busiLineCn = busiLineOptions.value[0]?.name || '';
  294 + // 处理重新提交逻辑
  295 + if (options.isRenew == 1 && options.tempKey) handleRenewOptions(options.tempKey);
353 296 });
354 297  
355 298 onReady(() => {
356   - if (workOrderFormRef.value) {
357   - workOrderFormRef.value.setRules(workOrderFormRules)
358   - }
359   - console.log('工单表单规则初始化完成')
360   -})
  299 + // 修复bug:nextTick确保表单ref一定挂载完成,再设置规则
  300 + nextTick(() => {
  301 + workOrderFormRef.value?.setRules(workOrderFormRules);
  302 + });
  303 +});
361 304  
362 305 onShow(() => {
363   - // 初始化工单名称列表
364   - orderNameList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('work_name'))
365   - // 初始化紧急程度列表
366   - pressingTypeList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('workorder_pressing_type'))
367   -})
  306 + // 初始化下拉字典数据
  307 + orderNameList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList(CONST.DICT_ORDER_NAME));
  308 + pressingTypeList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList(CONST.DICT_PRESSING_TYPE));
  309 +});
368 310  
369   -// ========== 核心方法 ==========
370   -const echoOrderData = (orderItem) => {
371   - // 回显业务线
372   - if (orderItem.busiLine) {
373   - workOrderForm.busiLineCn = busiLineMap.value[orderItem.busiLine];
  311 +// ========== 【核心业务方法】 ==========
  312 +/**
  313 + * 初始化工单线选项
  314 + */
  315 +const initBusiLineOptions = () => {
  316 + const rawBusiLines = userStore.userInfo?.user?.busiLine?.split(',') || [];
  317 + busiLineOptions.value = rawBusiLines.map(item => ({ name: busiLineEnToCn(item.trim()) }));
  318 +};
  319 +
  320 +/**
  321 + * 处理重新提交的参数和数据回显
  322 + */
  323 +const handleRenewOptions = async (tempKey) => {
  324 + isRenew.value = true;
  325 + try {
  326 + const orderData = uni.getStorageSync(tempKey);
  327 + if (orderData && typeof orderData === 'object') {
  328 + renewOrderData.value = orderData;
  329 + echoOrderData(orderData);
  330 + } else {
  331 + uni.showToast({ title: '工单数据不存在,无法重新提交', icon: 'none' });
  332 + setTimeout(toOrderListPage, 1000);
  333 + }
  334 + } catch (error) {
  335 + console.error('读取工单数据失败:', error);
  336 + uni.showToast({ title: '数据读取异常,无法重新提交', icon: 'none' });
  337 + setTimeout(toOrderListPage, 1000);
  338 + } finally {
  339 + uni.removeStorageSync(tempKey);
374 340 }
  341 +};
375 342  
376   - // 回显基础字段
  343 +/**
  344 + * 工单数据回显
  345 + */
  346 +const echoOrderData = (orderItem) => {
  347 + // 基础字段回显
  348 + console.log(orderItem)
  349 + console.log('123')
  350 + workOrderForm.busiLineCn = busiLineEnToCn(orderItem.busiLine);
377 351 workOrderForm.roadId = orderItem.roadId || 0;
378 352 workOrderForm.roadName = orderItem.roadName || '';
379 353 workOrderForm.workLocation = orderItem.lonLatAddress || orderItem.roadName || '';
380 354 workOrderForm.orderName = orderItem.orderName || '';
381 355 workOrderForm.pressingType = orderItem.pressingType || '';
382   - workOrderForm.pressingTypeName = uni.$dict.getDictLabel('workorder_pressing_type', orderItem.pressingType) || '';
  356 + workOrderForm.pressingTypeName = uni.$dict.getDictLabel(CONST.DICT_PRESSING_TYPE, orderItem.pressingType) || '';
383 357 workOrderForm.problemDesc = orderItem.remark || '';
384 358 workOrderForm.lat = orderItem.lat || 0;
385 359 workOrderForm.lon = orderItem.lon || 0;
386   - workOrderForm.expectedFinishDate = timeFormat(orderItem.expectedFinishDate, 'yyyy-mm-dd hh:MM:ss') || '';
387   -
388   - // 回显图片
389   - if (orderItem.problemsImgs && Array.isArray(orderItem.problemsImgs) && orderItem.problemsImgs.length > 0) {
390   - const imgList = orderItem.problemsImgs.map((imgUrl, index) => ({
391   - url: imgUrl,
392   - name: `renew_img_${index}`,
393   - status: 'success'
394   - }));
  360 + workOrderForm.expectedFinishDate = orderItem.expectedFinishDate?"":formatDate(orderItem.expectedFinishDate);
  361 +
  362 + // 图片回显 - 兼容异常格式
  363 + if (Array.isArray(orderItem.problemsImgs) && orderItem.problemsImgs.length) {
  364 + const imgList = orderItem.problemsImgs.map((url, idx) => ({ url, name: `renew_img_${idx}`, status: 'success' }));
395 365 problemImgs.imgList.value = imgList;
396 366 problemImgs.rawImgList.value = imgList;
397 367 }
398 368  
399 369 // 自动获取道路列表
400   - if (orderItem.lat && orderItem.lon) {
401   - getRoadListByBusiLine();
402   - }
  370 + if (orderItem.lat && orderItem.lon) getRoadListByBusiLine();
403 371 };
404 372  
405   -// 业务线切换事件
  373 +/**
  374 + * 业务线切换事件
  375 + */
406 376 const handleBusiLineChange = () => {
407 377 workOrderForm.roadName = '';
408 378 workOrderForm.roadId = 0;
409 379 roadNameList.value = [];
410   - if (workOrderForm.workLocation) {
411   - getRoadListByBusiLine();
412   - }
  380 + workOrderForm.workLocation && getRoadListByBusiLine();
413 381 };
414 382  
415   -// 获取道路列表(带业务线)
  383 +/**
  384 + * 根据坐标+业务线获取道路列表
  385 + */
416 386 const getRoadListByBusiLine = async () => {
417   - if (!workOrderForm.lat || !workOrderForm.lon) {
418   - return;
419   - }
420   - const busiLineEn = getBusiLineEnByCn(workOrderForm.busiLineCn);
421   - if (!busiLineEn) {
422   - uni.showToast({ title: '业务线标识异常', icon: 'none' });
423   - return;
424   - }
  387 + if (!workOrderForm.lat || !workOrderForm.lon) return;
  388 + const busiLineEn = busiLineCnToEn(workOrderForm.busiLineCn);
  389 + if (!busiLineEn) return uni.showToast({ title: '业务线选择异常', icon: 'none' });
425 390  
426 391 try {
427 392 uni.showLoading({ title: '获取道路名称中...' });
428   - const roadRes = await getRoadListByLatLng({
429   - busiLine: busiLineEn,
430   - latitude: workOrderForm.lat,
431   - longitude: workOrderForm.lon
432   - });
433   - uni.hideLoading();
434   - if (Array.isArray(roadRes)) {
435   - roadNameList.value = roadRes.map((item) => ({
436   - name: item.roadName || '',
437   - code: item.roadCode || '',
438   - id: item.roadId || 0
439   - }));
440   - } else {
441   - roadNameList.value = [{ name: '未查询到道路名称', code: '', id: 0 }];
442   - uni.showToast({ title: '未查询到该位置的道路信息', icon: 'none' });
443   - }
  393 + const roadRes = await getRoadListByLatLng({ busiLine: busiLineEn, latitude: workOrderForm.lat, longitude: workOrderForm.lon });
  394 + console.log(roadRes)
  395 + roadNameList.value = Array.isArray(roadRes)
  396 + ? roadRes.map(item => ({ name: item.roadName || '', code: item.roadCode || '', id: item.roadId || 0 }))
  397 + : [{ name: '未查询到道路名称', code: '', id: 0 }];
  398 + console.log(roadNameList)
  399 + !Array.isArray(roadRes) && uni.showToast({ title: '未查询到该位置的道路信息', icon: 'none' });
444 400 } catch (err) {
445   - uni.hideLoading();
446 401 console.error('获取道路名称失败:', err);
447   - uni.showToast({ title: '获取道路名称失败,请重试', icon: 'none' });
448 402 roadNameList.value = [{ name: '获取失败,请重新选择位置', code: '', id: 0 }];
  403 + uni.showToast({ title: '获取道路名称失败,请重试', icon: 'none' });
  404 + } finally {
  405 + uni.hideLoading();
449 406 }
450 407 };
451 408  
452   -// ========== 通用弹窗方法 ==========
  409 +// ========== 【通用下拉弹窗方法】 ==========
453 410 const handleActionSheetOpen = (type) => {
454 411 if (type === 'roadName' && !workOrderForm.workLocation) {
455   - uni.showToast({ title: '请先选择工单位置', icon: 'none' })
456   - return
  412 + return uni.showToast({ title: '请先选择工单位置', icon: 'none' });
457 413 }
458   -
459 414 const configMap = {
460   - roadName: {
461   - title: '请选择道路名称',
462   - list: roadNameList.value
463   - },
464   - orderName: {
465   - title: '请选择工单名称',
466   - list: orderNameList.value
467   - },
468   - pressingType: {
469   - title: '请选择紧急程度',
470   - list: pressingTypeList.value
471   - }
472   - }
473   -
474   - currentActionSheetData.type = type
475   - currentActionSheetData.title = configMap[type].title
476   - currentActionSheetData.list = configMap[type].list
477   - showActionSheet.value = true
478   -}
  415 + roadName: { title: '请选择道路名称', list: roadNameList.value },
  416 + orderName: { title: '请选择工单名称', list: orderNameList.value },
  417 + pressingType: { title: '请选择紧急程度', list: pressingTypeList.value }
  418 + };
  419 + Object.assign(currentActionSheetData, configMap[type], { type });
  420 + showActionSheet.value = true;
  421 +};
479 422  
480 423 const handleActionSheetClose = () => {
481   - showActionSheet.value = false
482   - currentActionSheetData.type = ''
483   - currentActionSheetData.list = []
484   - currentActionSheetData.title = ''
485   -}
  424 + showActionSheet.value = false;
  425 + Object.assign(currentActionSheetData, { type: '', list: [], title: '' });
  426 +};
486 427  
487 428 const handleActionSheetSelect = (e) => {
488   - const { type } = currentActionSheetData
  429 + const { type } = currentActionSheetData;
489 430 switch (type) {
490 431 case 'roadName':
491   - workOrderForm.roadName = e.name
492   - workOrderForm.roadId = e.code
493   - workOrderFormRef.value?.validateField('roadName')
494   - break
  432 + workOrderForm.roadName = e.name;
  433 + workOrderForm.roadId = e.code;
  434 + break;
495 435 case 'orderName':
496   - workOrderForm.orderName = e.name
497   - workOrderFormRef.value?.validateField('orderName')
498   - break
  436 + workOrderForm.orderName = e.name;
  437 + break;
499 438 case 'pressingType':
500   - workOrderForm.pressingType = e.value
501   - workOrderForm.pressingTypeName = e.name
502   - workOrderFormRef.value?.validateField('pressingTypeName')
503   - break
  439 + workOrderForm.pressingType = e.value;
  440 + workOrderForm.pressingTypeName = e.name;
  441 + break;
504 442 }
505   - showActionSheet.value = false
506   -}
507   -
508   -const navigateBack = () => {
509   - uni.reLaunch({
510   - url: '/pages-sub/problem/work-order-manage/index',
511   - fail: () => {
512   - uni.navigateBack({ delta: 2 });
513   - }
514   - });
515   -}
  443 + workOrderFormRef.value?.validateField(type === 'roadName' ? 'roadName' : type === 'orderName' ? 'orderName' : 'pressingTypeName');
  444 + handleActionSheetClose();
  445 +};
516 446  
517   -// 清除希望完成时间
518   -const clearExpectedFinishDate = ()=> {
519   - workOrderForm.expectedFinishDate = ''
520   -}
  447 +// ========== 【页面交互方法】 ==========
  448 +/** 清除希望完成时间 */
  449 +const clearExpectedFinishDate = () => workOrderForm.expectedFinishDate = '';
  450 +/** 隐藏软键盘 */
  451 +const hideKeyboard = () => uni.hideKeyboard();
  452 +/** 时间选择确认 */
  453 +const expectedFinishDateConfirm = (e) => {
  454 + workOrderForm.expectedFinishDate = formatDate(e.value);
  455 + show.value = false;
  456 +};
521 457  
522   -// 选择工单位置
  458 +/** 选择工单位置 */
523 459 const chooseWorkLocation = () => {
524 460 uni.chooseLocation({
525 461 success: async (res) => {
526   - workOrderForm.roadName = ''
527   - workOrderForm.roadId = 0
528   - roadNameList.value = []
529   -
530   - workOrderForm.workLocation = res.name
531   - workOrderForm.lat = res.latitude
532   - workOrderForm.lon = res.longitude
533   -
534   - workOrderFormRef.value?.validateField('workLocation')
535   - workOrderFormRef.value?.validateField('roadName')
536   -
  462 + workOrderForm.roadName = '';
  463 + workOrderForm.roadId = 0;
  464 + roadNameList.value = [];
  465 + workOrderForm.workLocation = res.name;
  466 + workOrderForm.lat = res.latitude;
  467 + workOrderForm.lon = res.longitude;
  468 + workOrderFormRef.value?.validateField(['workLocation', 'roadName']);
537 469 await getRoadListByBusiLine();
538 470 },
539 471 fail: (err) => {
540   - console.error('选择位置失败:', err)
541   - uni.showToast({ title: '选择位置失败:' + err.errMsg, icon: 'none' })
  472 + console.error('选择位置失败:', err);
  473 + const errMsg = err.errMsg.includes('auth deny') ? '请开启位置权限后重试' : '选择位置失败,请重试';
  474 + uni.showToast({ title: errMsg, icon: 'none' });
542 475 }
543 476 })
544   -}
545   -
546   -// 完成时间确认
547   -const expectedFinishDateConfirm = (e) => {
548   - workOrderForm.expectedFinishDate = timeFormat(e.value, 'yyyy-mm-dd hh:MM:ss')
549   - show.value = false
550   -}
  477 +};
  478 +// 新增页返回直接回列表页
  479 +onBackPress(() => {
  480 + uni.redirectTo({
  481 + url: '/pages-sub/problem/regional-order-manage/index'
  482 + })
  483 + return true;
  484 +})
551 485  
552   -// 隐藏键盘
553   -const hideKeyboard = () => {
554   - uni.hideKeyboard()
555   -}
  486 +// ========== 【封装:统一接口调用方法】 ==========
  487 +const callApiByRole = async (params, isRenewFlag) => {
  488 + if (isRenewFlag) {
  489 + if (USER_ROLES.includes(CONST.ROLE.MANAGER)) return await daquUniversalApproval(params);
  490 + if (USER_ROLES.includes(CONST.ROLE.INSPECTOR)) return await dcyUniversalApproval(params);
  491 + if (USER_ROLES.includes(CONST.ROLE.PATROL)) return await qyUniversalApproval(params);
  492 + } else {
  493 + if (USER_ROLES.includes(CONST.ROLE.MANAGER)) return await daquWorkorderCreate(params);
  494 + if (USER_ROLES.includes(CONST.ROLE.INSPECTOR)) return await dcyWorkorderCreate(params);
  495 + if (USER_ROLES.includes(CONST.ROLE.PATROL)) return await qyWorkorderCreate(params);
  496 + }
  497 +};
556 498  
557   -// 提交工单
  499 +// ========== 【核心:提交工单】 ==========
558 500 const submitWorkOrder = async () => {
559 501 try {
560   - await workOrderFormRef.value.validate()
561   -
562   - const busiLineEn = getBusiLineEnByCn(workOrderForm.busiLineCn);
563   - if (!busiLineEn) {
564   - uni.showToast({ title: '业务线选择异常,请重新选择', icon: 'none' });
565   - return;
566   - }
567   -
568   - const commonSubmitData = {
569   - // roadId: workOrderForm.roadId,
570   - // roadName: workOrderForm.roadName,
571   -
572   - // roadId: 550,
573   - // roadName:'东明胡同(三海)',
574   -
575   - roadId: 551,
576   - roadName:'地安门西大街51号',
577   - //
578   - // roadId: 553,
579   - // roadName:'什刹海前海南沿',
580   -
  502 + // 表单校验
  503 + await workOrderFormRef.value.validate();
  504 + const busiLineEn = busiLineCnToEn(workOrderForm.busiLineCn);
  505 + if (!busiLineEn) return uni.showToast({ title: '业务线选择异常,请重新选择', icon: 'none' });
  506 +
  507 + // 组装公共提交参数
  508 + const baseParams = {
  509 + roadId: workOrderForm.roadId,
  510 + roadName: workOrderForm.roadName,
581 511 problemsImgs: problemImgs.getSuccessImgUrls(),
582 512 remark: workOrderForm.problemDesc.trim(),
583 513 latLonType: 2,
... ... @@ -586,77 +516,37 @@ const submitWorkOrder = async () =&gt; {
586 516 lonLatAddress: workOrderForm.workLocation,
587 517 pressingType: workOrderForm.pressingType,
588 518 orderName: workOrderForm.orderName,
589   - expectedFinishDate: workOrderForm.expectedFinishDate==""?"":new Date(workOrderForm.expectedFinishDate).getTime(),
  519 + expectedFinishDate: dateToTimestamp(workOrderForm.expectedFinishDate),
590 520 sourceId: 1,
591 521 sourceName: workOrderForm.busiLineCn,
592 522 busiLine: busiLineEn
593   - }
594   -
595   - uni.showLoading({ title: '提交中...' })
596   - let res
597   -
598   - if (isRenew.value) {
599   - const renewSubmitData = {
600   - workerDataId: renewOrderData.value.id,
601   - taskKey: renewOrderData.value.taskKey,
602   - taskId: renewOrderData.value.taskId,
603   - operateType: nextStepMap[renewOrderData.value.taskKey]?.operateTypeRenew || '',
604   - agree: 0,
605   - reason: workOrderForm.problemDesc.trim(),
606   - ...commonSubmitData
607   - }
608   -
609   - if( userStore.userInfo.roles.includes('regional_manager')){ // 大区经理
610   - res = await daquUniversalApproval(renewSubmitData)
611   - }
612   -
613   - if( userStore.userInfo.roles.includes('Inspector_global')){ // 督察员
614   - res = await dcyUniversalApproval(renewSubmitData)
615   - }
616   -
617   - if( userStore.userInfo.roles.includes('patrol_global')){ // 全域巡查员
618   - res = await qyUniversalApproval(renewSubmitData)
619   - }
620   -
621   - } else {
622   -
623   - if( userStore.userInfo.roles.includes('regional_manager')){ // 大区经理
624   - res = await daquWorkorderCreate(commonSubmitData)
625   - }
626   -
627   - if( userStore.userInfo.roles.includes('Inspector_global')){ // 督察员
628   - res = await dcyWorkorderCreate(commonSubmitData)
629   - }
630   -
631   - if( userStore.userInfo.roles.includes('patrol_global')){ // 全域巡查员
632   - res = await qyWorkorderCreate(commonSubmitData)
633   - }
634   -
635   -
636   - }
637   -
638   - uni.hideLoading()
639   - uni.showToast({
640   - title: isRenew.value ? '重新提交成功' : '工单提交成功',
641   - icon: 'success',
642   - duration: 1000
643   - })
644   -
645   - setTimeout(() => {
646   - uni.reLaunch({
647   - url: '/pages-sub/problem/regional-order-manage/index'
648   - })
649   - }, 1000)
  523 + };
  524 +
  525 + uni.showLoading({ title: '提交中...', mask: true });
  526 + // 区分:重新提交 / 新增工单
  527 + const submitParams = isRenew.value
  528 + ? {
  529 + workerDataId: renewOrderData.value.id,
  530 + taskKey: renewOrderData.value.taskKey,
  531 + taskId: renewOrderData.value.taskId,
  532 + operateType: nextStepMap[renewOrderData.value.taskKey]?.operateTypeRenew || '',
  533 + agree: 0,
  534 + reason: workOrderForm.problemDesc.trim(),
  535 + ...baseParams
  536 + }
  537 + : baseParams;
  538 +
  539 + // 统一调用接口
  540 + await callApiByRole(submitParams, isRenew.value);
  541 +
  542 + // 提交成功处理
  543 + uni.showToast({ title: isRenew.value ? '重新提交成功' : '工单提交成功', icon: 'success', duration: 1000 });
  544 + setTimeout(toOrderListPage, 1000);
650 545 } catch (error) {
651   - uni.hideLoading()
652   -
  546 + uni.hideLoading();
653 547 if (!Array.isArray(error)) {
654   - console.error(isRenew.value ? '工单重新提交失败:' : '工单提交失败:', error)
655   - uni.showToast({
656   - title: isRenew.value ? error.msg : error.msg,
657   - icon: 'none',
658   - duration: 2000
659   - })
  548 + console.error(isRenew.value ? '工单重新提交失败:' : '工单提交失败:', error);
  549 + uni.showToast({ title: error.msg || '提交失败,请重试', icon: 'none', duration: 2000 });
660 550 }
661 551 }
662 552 }
... ... @@ -667,6 +557,7 @@ const submitWorkOrder = async () =&gt; {
667 557 .page-container {
668 558 min-height: 100vh;
669 559 padding-bottom: 100rpx; // 给底部按钮留空间
  560 + background: #fafafa;
670 561 }
671 562  
672 563 // 工单表单内容容器
... ... @@ -674,19 +565,18 @@ const submitWorkOrder = async () =&gt; {
674 565 background: #fff;
675 566 }
676 567  
677   -// 派单情况分组样式(核心:增加顶部间距)
  568 +// 派单情况分组样式
678 569 .dispatch-group {
679   -
680   - padding-top: 20rpx; // 分组内顶部内边距,增强视觉区分
681   -
682   -}
683   -
684   -// 派单情况标题样式
685   -.dispatch-title {
686   - font-size: 32rpx;
687   - font-weight: 600;
688   - color:$u-primary;
689   - margin-bottom: 20rpx;
690   - padding-left: 10rpx;
  570 + padding-top: 24rpx;
  571 + margin-top: 8rpx;
  572 + border-top: 1px solid #f5f5f5;
  573 +
  574 + .dispatch-title {
  575 + font-size: 32rpx;
  576 + font-weight: 600;
  577 + color: $u-primary;
  578 + margin-bottom: 20rpx;
  579 + padding-left: 10rpx;
  580 + }
691 581 }
692 582 </style>
693 583 \ No newline at end of file
... ...
pages-sub/problem/regional-order-manage/add-patrol-order.vue
... ... @@ -101,7 +101,7 @@
101 101  
102 102 <script setup>
103 103 import { ref, reactive } from 'vue'
104   -import { onReady, onShow, onLoad } from '@dcloudio/uni-app';
  104 +import { onReady, onShow, onLoad, onBackPress } from '@dcloudio/uni-app';
105 105 import { useUploadImgs } from '@/common/utils/useUploadImgs'
106 106 import { qyUniversalApproval, qyWorkorderCreate } from '@/api/regional-order-manage/regional-order-manage'
107 107 import { timeFormat } from '@/uni_modules/uview-plus'
... ... @@ -237,6 +237,13 @@ onShow(() =&gt; {
237 237 // 初始化工单名称列表
238 238 orderNameList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('work_name'))
239 239 })
  240 +// 新增页返回直接回列表页
  241 +onBackPress(() => {
  242 + uni.redirectTo({
  243 + url: '/pages-sub/problem/regional-order-manage/index'
  244 + })
  245 + return true;
  246 +})
240 247  
241 248 // ========== 核心方法 ==========
242 249 const echoOrderData = (orderItem) => {
... ...
pages-sub/problem/regional-order-manage/distribution-order.vue
... ... @@ -194,7 +194,7 @@
194 194  
195 195 <script setup>
196 196 import { ref, reactive } from 'vue'
197   -import { onReady, onShow, onLoad } from '@dcloudio/uni-app';
  197 +import { onReady, onShow, onLoad, onBackPress } from '@dcloudio/uni-app';
198 198 import { useUploadImgs } from '@/common/utils/useUploadImgs'
199 199 import { getRoadListByLatLng } from '@/api/common'
200 200 import { daquUniversalApproval, daquWorkorderCreate, qyUniversalApproval } from '@/api/regional-order-manage/regional-order-manage'
... ... @@ -357,6 +357,14 @@ onShow(() =&gt; {
357 357 pressingTypeList.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('workorder_pressing_type'))
358 358 })
359 359  
  360 +// 新增页返回直接回列表页
  361 +onBackPress(() => {
  362 + uni.redirectTo({
  363 + url: '/pages-sub/problem/regional-order-manage/index'
  364 + })
  365 + return true;
  366 +})
  367 +
360 368 // ========== 核心方法 ==========
361 369 const echoOrderData = (orderItem) => {
362 370 // 回显业务线
... ... @@ -374,7 +382,7 @@ const echoOrderData = (orderItem) =&gt; {
374 382 workOrderForm.problemDesc = orderItem.remark || '';
375 383 workOrderForm.lat = orderItem.lat || 0;
376 384 workOrderForm.lon = orderItem.lon || 0;
377   - workOrderForm.expectedFinishDate = orderItem.expectedFinishDate=='' ? '':timeFormat(orderItem.expectedFinishDate, 'yyyy-mm-dd hh:MM:ss') ;
  385 + workOrderForm.expectedFinishDate = orderItem.expectedFinishDate? timeFormat(orderItem.expectedFinishDate, 'yyyy-mm-dd hh:MM:ss'):'' ;
378 386  
379 387 // 回显图片【核心:问题照片赋值】
380 388 if (orderItem.problemsImgs && Array.isArray(orderItem.problemsImgs) && orderItem.problemsImgs.length > 0) {
... ...
pages-sub/problem/regional-order-manage/index.vue
... ... @@ -3,7 +3,6 @@
3 3 <!-- 顶部固定区域 -->
4 4 <up-sticky>
5 5 <view class="header-wrap">
6   - <!-- 第一行:u-tabs 待办/已办切换 :scrollable="false"-->
7 6 <up-tabs
8 7 v-model="activeTab"
9 8 :list="tabList"
... ... @@ -12,10 +11,8 @@
12 11 font-size="30rpx"
13 12 @click="handleTabChange"
14 13 />
15   -
16 14 <!-- 第二行:下拉框 + 搜索框 -->
17 15 <view class="search-header">
18   - <!-- 左侧下拉框 -->
19 16 <view class="select-wrap common-text-color">
20 17 <up-select
21 18 v-model:current="selectedSortValue"
... ... @@ -26,8 +23,6 @@
26 23 :style="{ flex: 1 }"
27 24 />
28 25 </view>
29   -
30   - <!-- 右侧搜索框 -->
31 26 <view class="search-input-wrap">
32 27 <up-search
33 28 v-model="searchValue"
... ... @@ -47,24 +42,23 @@
47 42  
48 43 <!-- 列表容器 -->
49 44 <z-paging
50   - ref="paging"
  45 + ref="pagingRef"
51 46 v-model="orderList"
52 47 @query="queryList"
53 48 :auto-show-system-loading="true"
54   -
55 49 >
56 50 <template #empty>
57   - <empty-view/>
  51 + <empty-view />
58 52 </template>
59 53  
60 54 <view class="common-card-list" style="padding-top: 200rpx;padding-bottom: 30rpx">
61 55 <!-- 待办工单卡片 -->
62 56 <up-card
63   - v-if="activeTab == 0"
  57 + v-if="activeTab === 0"
64 58 :border="false"
65 59 :foot-border-top="false"
66 60 v-for="(item, index) in orderList"
67   - :key="`todo_${item.orderNo}_${index}`"
  61 + :key="`todo_${item.id}_${index}`"
68 62 :show-head="false"
69 63 class="order-card"
70 64 >
... ... @@ -86,7 +80,7 @@
86 80 <view class="u-body-item-title">情况描述:</view>
87 81 <view class="u-line-1 u-body-value">{{ item.remark || '无' }}</view>
88 82 </view>
89   - <view class="u-body-item u-flex common-item-center common-justify-between">
  83 + <view class="u-body-item u-flex common-item-center common-justify-between">
90 84 <view class="u-body-item-title">紧急程度:</view>
91 85 <view class="u-line-1 u-body-value">
92 86 {{ uni.$dict.getDictLabel('workorder_pressing_type', item.pressingType) }}
... ... @@ -101,20 +95,10 @@
101 95 <view class="u-line-1 u-body-value">{{ timeFormat(item.createTime, 'yyyy-mm-dd hh:MM:ss') }}</view>
102 96 </view>
103 97 <!-- 操作按钮行 -->
104   -
105 98 <view class="u-body-item u-flex common-justify-between common-item-center mt-20">
106   - <up-button type="warning" size="mini" @click="handleReject(item)"
107   - v-show="nextStepMap[item.taskKey].backShow">回退
108   - </up-button>
109   -
110   - <up-button type="success" size="mini" @click="handleRenew(item)"
111   - v-show="nextStepMap[item.taskKey].renewShow">重新提交
112   - </up-button>
113   -
114   - <up-button type="primary" size="mini" @click="handleProcess(item)">{{
115   - nextStepMap[item.taskKey].btnText
116   - }}
117   - </up-button>
  99 + <up-button type="warning" size="mini" @click="handleReject(item)" v-show="nextStepMap[item.taskKey].backShow">回退</up-button>
  100 + <up-button type="success" size="mini" @click="handleRenew(item)" v-show="nextStepMap[item.taskKey].renewShow">重新提交</up-button>
  101 + <up-button type="primary" size="mini" @click="handleProcess(item)">{{ nextStepMap[item.taskKey].btnText }}</up-button>
118 102 <up-button type="info" size="mini" @click="handleDetail(item)">详情</up-button>
119 103 </view>
120 104 </view>
... ... @@ -123,11 +107,11 @@
123 107  
124 108 <!-- 已办工单卡片和我发起的 -->
125 109 <up-card
126   - v-if="activeTab == 2||activeTab == 1"
  110 + v-if="activeTab === 1 || activeTab === 2"
127 111 :border="false"
128 112 :foot-border-top="false"
129 113 v-for="(item, index) in orderList"
130   - :key="`done_${item.orderNo}_${index}`"
  114 + :key="`done_${item.id}_${index}`"
131 115 :show-head="false"
132 116 class="order-card"
133 117 >
... ... @@ -149,12 +133,11 @@
149 133 <view class="u-body-item-title">情况描述:</view>
150 134 <view class="u-line-1 u-body-value">{{ item.remark || '无' }}</view>
151 135 </view>
152   -
153 136 <view class="u-body-item u-flex common-justify-between common-item-center">
154 137 <view class="u-body-item-title">
155 138 紧急程度:{{ uni.$dict.getDictLabel('workorder_pressing_type', item.pressingType) }}
156 139 </view>
157   - <view class=" ">
  140 + <view>
158 141 <up-button type="primary" size="mini" @click="handleDetail(item)">工单详情</up-button>
159 142 </view>
160 143 </view>
... ... @@ -172,21 +155,12 @@
172 155 </view>
173 156 </z-paging>
174 157  
175   - <!-- 底部新增工单按钮(仅巡查员显示) -->
176   - <!-- <view v-if="isInspector" class="fixed-bottom-btn-wrap">-->
177   - <!-- <up-button type="primary" size="large" @click="handleAddOrder">-->
178   - <!-- 新增工单-->
179   - <!-- </up-button>-->
180   - <!-- </view>-->
181   -
  158 + <!-- 底部新增工单按钮 -->
182 159 <view class="fixed-bottom-btn-wrap" v-if="isAi">
183   - <up-button type="primary" size="large" @click="handleAddOrder">
184   - 新增工单
185   - </up-button>
  160 + <up-button type="primary" size="large" @click="handleAddOrder">新增工单</up-button>
186 161 </view>
187 162  
188   -
189   - <!-- 回退原因弹窗:替换为up-modal(核心修改) -->
  163 + <!-- 回退原因弹窗 -->
190 164 <up-modal
191 165 :show="rejectModalShow"
192 166 title="回退原因"
... ... @@ -197,7 +171,6 @@
197 171 @confirm="confirmReject"
198 172 >
199 173 <view class="reject-modal-content">
200   - <!-- 回退原因 必填textarea -->
201 174 <up-textarea
202 175 v-model.trim="rejectReason"
203 176 placeholder="请输入回退原因(必填)"
... ... @@ -206,11 +179,10 @@
206 179 maxlength="200"
207 180 class="reject-textarea"
208 181 />
209   - <!-- 上传图片(选填)- 按照参考页面改造 -->
210 182 <view class="upload-wrap mt-20">
211 183 <view class="upload-title">上传图片(选填)</view>
212 184 <up-upload
213   - :file-list="rejectImgs.rawImgList.value|| []"
  185 + :file-list="rejectImgs.rawImgList.value || []"
214 186 @after-read="rejectImgs.uploadImgs"
215 187 @delete="rejectImgs.deleteImg"
216 188 multiple
... ... @@ -224,7 +196,7 @@
224 196 </view>
225 197 </up-modal>
226 198  
227   - <!-- 验收弹窗 up-modal(含图片上传) -->
  199 + <!-- 验收弹窗 -->
228 200 <up-modal
229 201 :show="acceptModalShow"
230 202 title="验收"
... ... @@ -235,15 +207,12 @@
235 207 @confirm="handleAcceptModalConfirm"
236 208 >
237 209 <view class="accept-modal-content">
238   - <!-- 第一行:单选框(通过/不通过,默认通过) -->
239 210 <view class="radio-group-wrap">
240 211 <up-radio-group v-model="acceptRadioValue">
241 212 <up-radio name="0" label="通过"></up-radio>
242 213 <up-radio name="1" label="不通过"></up-radio>
243 214 </up-radio-group>
244 215 </view>
245   -
246   - <!-- 第二行:必填textarea,最多200字 -->
247 216 <view class="textarea-wrap mt-30">
248 217 <up-textarea
249 218 v-model.trim="acceptReason"
... ... @@ -254,15 +223,12 @@
254 223 count
255 224 />
256 225 </view>
257   -
258   - <!-- 验收图片上传(选填,参考回退弹窗样式) -->
259 226 <view class="upload-wrap mt-20">
260 227 <view class="upload-title">上传验收图片(选填)</view>
261 228 <up-upload
262 229 :file-list="acceptImgs.rawImgList.value || []"
263 230 @after-read="acceptImgs.uploadImgs"
264 231 @delete="acceptImgs.deleteImg"
265   -
266 232 multiple
267 233 width="70"
268 234 height="70"
... ... @@ -284,124 +250,171 @@ import {
284 250 myBuzSimplePage,
285 251 todoBuzSimplePage,
286 252 doneBuzSimplePage,
287   - qyWorkorderCreate,
288 253 qyUniversalApproval,
289 254 daquUniversalApproval,
290 255 dcyUniversalApproval
291 256 } from '@/api/regional-order-manage/regional-order-manage'
292   -// 从用户store获取角色信息
293 257 import { useUserStore } from '@/pinia/user';
294 258 import { nextStepMap, buzStatusMap } from '@/common/utils/common'
295   -// 引入图片上传组合式函数(与参考页面一致)
296 259 import { useUploadImgs } from '@/common/utils/useUploadImgs'
297   -// ========== 状态管理 ==========
  260 +
  261 +// ========== 全局实例 & 常量 ==========
298 262 const userStore = useUserStore();
299   -// 标签页切换
  263 +const USER_ROLES = userStore.userInfo?.roles || [];
  264 +
  265 +// ========== 基础状态 (按模块分组,语义化更强) ==========
  266 +// tab切换
300 267 const activeTab = ref(0); // 0-待办 1-我发起的 2-已办
301   -const tabList = ref([
302   - {name: '待办'},
303   - {name: '我发起的任务'},
304   - {name: '已办'}
305   -]);
306   -// 排序下拉框
  268 +const tabList = ref([{name: '待办'}, {name: '我发起的任务'}, {name: '已办'}]);
  269 +// 排序与搜索
307 270 const selectedSortValue = ref(1);
308 271 const sortOptions = ref([
309   - {name: '位置', id: 1},
310   - {name: '名称', id: 2},
311   - {name: '描述', id: 3},
312   - {name: '编号', id: 4},
  272 + {name: '位置', id: 1}, {name: '名称', id: 2}, {name: '描述', id: 3}, {name: '编号', id: 4},
313 273 ]);
314   -// 搜索
315 274 const searchValue = ref('');
316   -// 分页
317   -const paging = ref(null);
  275 +// 分页相关
  276 +const pagingRef = ref(null);
318 277 const orderList = ref([]);
319   -// 角色控制(巡查员显示新增按钮)
  278 +
  279 +// ========== 角色权限计算属性 (修复核心bug: 原代码取反逻辑写反) ==========
320 280 const isAi = computed(() => {
321   - // patrol_global 全域巡查员
322   - // regional_manager 大区经理
323   - // AI_dispatcher AI工单派发人员
324   - // 增加可选链,避免用户信息不存在报错
325   - console.log('123')
326   - console.log(!userStore.userInfo?.roles?.includes('AI_dispatcher'))
327   - return !userStore.userInfo?.roles?.includes('AI_dispatcher') ;
328   - // return true
  281 + // AI工单派发人员 不显示新增按钮,其他角色显示
  282 + return !USER_ROLES.includes('AI_dispatcher');
329 283 });
330   -// 回退弹窗相关
331   -const rejectModalShow = ref(false); // 回退modal显示开关
332   -const rejectReason = ref(''); // 回退原因
333   -const currentRejectItem = ref(null); // 当前回退工单
334   -// 回退图片上传配置(与参考页面风格一致)
  284 +
  285 +// ========== 回退弹窗相关 ==========
  286 +const rejectModalShow = ref(false);
  287 +const rejectReason = ref('');
  288 +const currentRejectItem = ref(null);
335 289 const rejectImgs = useUploadImgs({
336   - maxCount: 3, // 最多上传3张
337   - uploadText: '选择回退图片', // 自定义上传提示文字
338   - sizeType: ['compressed'], // 仅上传压缩图
339   - formRef: null, // 该弹窗无表单校验
340   - fieldName: 'rejectImgs' // 自定义字段名
  290 + maxCount: 3, uploadText: '选择回退图片', sizeType: ['compressed'], formRef: null, fieldName: 'rejectImgs'
341 291 })
342   -// ========== 验收弹窗相关状态(含图片上传) ==========
343   -const acceptModalShow = ref(false); // 验收弹窗显示开关
344   -const acceptRadioValue = ref('0'); // 单选框值,默认0(通过)
345   -const acceptReason = ref(''); // 验收原因
346   -const currentAcceptItem = ref(null); // 当前验收的工单项
347   -// 验收图片上传配置(独立实例,参考回退弹窗)
  292 +
  293 +// ========== 验收弹窗相关 ==========
  294 +const acceptModalShow = ref(false);
  295 +const acceptRadioValue = ref('0'); // 默认通过
  296 +const acceptReason = ref('');
  297 +const currentAcceptItem = ref(null);
348 298 const acceptImgs = useUploadImgs({
349   - maxCount: 3, // 最多上传3张,与回退弹窗一致
350   - uploadText: '选择验收图片', // 自定义上传提示文字
351   - sizeType: ['compressed'], // 仅上传压缩图,优化性能
352   - formRef: null, // 验收弹窗无表单校验
353   - fieldName: 'acceptImgs' // 自定义字段名,区分回退图片
  299 + maxCount: 3, uploadText: '选择验收图片', sizeType: ['compressed'], formRef: null, fieldName: 'acceptImgs'
354 300 })
  301 +
  302 +// ========== 公共封装方法 (核心优化:消灭重复代码) ==========
  303 +/**
  304 + * 生成统一的临时存储key
  305 + * @param {String} prefix 前缀标识
  306 + * @returns {String} 唯一key
  307 + */
  308 +const generateTempKey = (prefix = 'order') => {
  309 + return `${prefix}_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
  310 +};
  311 +
  312 +/**
  313 + * 存储工单数据到本地缓存
  314 + * @param {Object} item 工单数据
  315 + * @param {String} prefix key前缀
  316 + * @returns {String|null} 成功返回key,失败返回null
  317 + */
  318 +const setOrderStorage = (item, prefix) => {
  319 + if (!item?.id) return null;
  320 + const tempKey = generateTempKey(prefix);
  321 + try {
  322 + uni.setStorageSync(tempKey, item);
  323 + return tempKey;
  324 + } catch (error) {
  325 + console.error('存储工单数据失败:', error);
  326 + uni.showToast({title: '数据存储异常,请重试', icon: 'none'});
  327 + return null;
  328 + }
  329 +};
  330 +
  331 +/**
  332 + * 获取分页请求公共参数
  333 + * @param {Number} pageNo 页码
  334 + * @param {Number} pageSize 页大小
  335 + * @returns {Object} 请求参数
  336 + */
  337 +const getQueryParams = (pageNo, pageSize) => {
  338 + return {
  339 + searchContent: searchValue.value.trim() || '',
  340 + pageNo,
  341 + pageSize,
  342 + type: selectedSortValue.value
  343 + };
  344 +};
  345 +
  346 +/**
  347 + * 统一调用审批接口
  348 + * @param {Object} params 请求参数
  349 + * @param {String} taskKey 工单任务key
  350 + * @returns {Promise} 接口请求Promise
  351 + */
  352 +const callApprovalApi = async (params, taskKey) => {
  353 + if (taskKey === 'shRegionManager') return await dcyUniversalApproval(params);
  354 + if (taskKey === 'regionManager') return await qyUniversalApproval(params);
  355 + // 根据角色匹配对应接口
  356 + if (USER_ROLES.includes('regional_manager')) return await daquUniversalApproval(params);
  357 + if (USER_ROLES.includes('Inspector_global')) return await dcyUniversalApproval(params);
  358 + if (USER_ROLES.includes('patrol_global')) return await qyUniversalApproval(params);
  359 +};
  360 +
  361 +/**
  362 + * 统一跳转工单页面
  363 + * @param {String} path 页面路径
  364 + * @param {Object} query 拼接参数
  365 + */
  366 +const navToOrderPage = (path, query = {}) => {
  367 + const queryStr = Object.keys(query).map(k => `${k}=${query[k]}`).join('&');
  368 + uni.redirectTo({ url: `${path}${queryStr ? '?' + queryStr : ''}` });
  369 +};
  370 +
  371 +/**
  372 + * 刷新列表 - 统一封装,避免空指针
  373 + */
  374 +const refreshOrderList = () => {
  375 + pagingRef.value && pagingRef.value.reload();
  376 +};
  377 +
  378 +// ========== 业务核心方法 ==========
355 379 // 分页查询列表
356 380 const queryList = async (pageNo, pageSize) => {
357 381 try {
358   - const apiParams = {
359   - searchContent: searchValue.value.trim() || '',
360   - pageNo,
361   - pageSize,
362   - type: selectedSortValue.value // 1-位置 2-工单名称 3-情况描述 4-工单编号
363   - };
  382 + const params = getQueryParams(pageNo, pageSize);
364 383 let res;
365   - if (activeTab.value == 0) {
366   - // 待办工单
367   - res = await todoBuzSimplePage(apiParams);
368   - } else if (activeTab.value == 1) {
369   - // 我发起的任务
370   - res = await myBuzSimplePage(apiParams);
371   - } else {
372   - // 已办工单
373   - res = await doneBuzSimplePage(apiParams);
374   - }
375   - // 适配z-paging分页
376   - paging.value.complete(res.list, res.total);
  384 + if (activeTab.value === 0) res = await todoBuzSimplePage(params);
  385 + else if (activeTab.value === 1) res = await myBuzSimplePage(params);
  386 + else res = await doneBuzSimplePage(params);
  387 + pagingRef.value.complete(res?.list || [], res?.total || 0);
377 388 } catch (error) {
378 389 console.error('加载工单失败:', error);
379   - paging.value?.complete(false);
  390 + pagingRef.value?.complete(false);
380 391 uni.showToast({title: '加载失败,请重试', icon: 'none'});
381 392 }
382 393 };
383   -// ========== 事件处理 ==========
  394 +
384 395 // 标签页切换
385 396 const handleTabChange = (item) => {
386   - orderList.value = [];
387   - console.log(item)
388 397 activeTab.value = item.index;
389   - paging.value?.reload(); // 切换标签页刷新列表
  398 + orderList.value = [];
  399 + refreshOrderList();
390 400 };
  401 +
391 402 // 排序变更
392 403 const handleSortChange = (val) => {
393 404 selectedSortValue.value = val.id;
394 405 searchValue.value = '';
395   - paging.value?.reload(); // 排序变更刷新列表
  406 + refreshOrderList();
396 407 };
  408 +
397 409 // 搜索
398 410 const handleSearch = (val) => {
399 411 searchValue.value = val;
400   - paging.value?.reload(); // 搜索刷新列表
  412 + refreshOrderList();
401 413 };
  414 +
402 415 // 工单详情
403 416 const handleDetail = (item) => {
404   - // 0-待办 1我发起的- 2-已办
  417 + if (!item?.taskId) return uni.showToast({title: '工单信息异常', icon: 'none'});
405 418 uni.navigateTo({
406 419 url: `/pages-sub/problem/regional-order-manage/order-detail?taskId=${item.taskId}&activeTab=${activeTab.value}&processInstanceId=${item.processInstanceId}`,
407 420 events: {
... ... @@ -414,302 +427,163 @@ const handleDetail = (item) =&gt; {
414 427 }
415 428 }
416 429 });
  430 + // navToOrderPage('/pages-sub/problem/regional-order-manage/order-detail', {
  431 + // taskId: item.taskId,
  432 + // activeTab: activeTab.value,
  433 + // processInstanceId: item.processInstanceId
  434 + // }, {
  435 + // needRefresh: () => refreshOrderList()
  436 + // });
  437 +
  438 +
417 439 };
418   -// 生成临时key
419   -const generateTempKey = () => {
420   - return 'renew_order_' + Date.now() + '_' + Math.floor(Math.random() * 10000);
421   -};
422   -// 待办-重新提交工单(改造后:大数据存本地,仅传唯一标识)
  440 +
  441 +// 重新提交工单
423 442 const handleRenew = (item) => {
  443 + const tempKey = setOrderStorage(item, 'renew_order');
  444 + if (!tempKey) return;
424 445  
425   - // 1. 生成唯一临时标识
426   - const tempKey = generateTempKey();
427   - // 2. 将完整工单数据存入本地临时存储(同步存储,确保数据立即生效)
428   - try {
429   - console.log(item)
430   - console.log('123')
431   - uni.setStorageSync(tempKey, item);
432   - } catch (error) {
433   - console.error('存储工单数据失败:', error);
434   - uni.showToast({title: '数据存储异常,无法重新提交', icon: 'none'});
435   - return;
436   - }
  446 + const pageUrl = USER_ROLES.includes('patrol_global')
  447 + ? '/pages-sub/problem/regional-order-manage/add-patrol-order'
  448 + : '/pages-sub/problem/regional-order-manage/add-order';
437 449  
438   - if (userStore.userInfo?.roles.includes('patrol_global')) { // 全域巡查员
439   - uni.navigateTo({
440   - url: `/pages-sub/problem/regional-order-manage/add-patrol-order?isRenew=1&tempKey=${tempKey}`
441   - });
442   - }
443   - if (userStore.userInfo?.roles.includes('regional_manager')) { // 大区经理
444   - uni.navigateTo({
445   - url: `/pages-sub/problem/regional-order-manage/add-order?isRenew=1&tempKey=${tempKey}`
446   - });
447   - }
448   - if (userStore.userInfo?.roles.includes('Inspector_global')) { // 督察员
449   - uni.navigateTo({
450   - url: `/pages-sub/problem/regional-order-manage/add-order?isRenew=1&tempKey=${tempKey}`
451   - });
452   - }
453   - // // 3. URL 仅传递「唯一标识」和「重新提交标记」(数据量极小,无长度问题)
454   - // uni.navigateTo({
455   - // url: `/pages-sub/problem/regional-order-manage/add-patrol-order?isRenew=1&tempKey=${tempKey}`
456   - // });
  450 + navToOrderPage(pageUrl, { isRenew: 1, tempKey });
457 451 };
458   -// 待办-处理工单
  452 +
  453 +// 处理工单核心逻辑
459 454 const handleProcess = async (item) => {
460   - console.log(nextStepMap[item.taskKey].name)
  455 + if (!item) return uni.showToast({title: '工单信息异常', icon: 'none'});
  456 + const stepName = nextStepMap[item.taskKey]?.name;
  457 + let resData
461 458 try {
462   - if (nextStepMap[item.taskKey]?.name == '大区经理分配') {
463   - // ① 生成唯一临时key(统一规则,避免冲突)
464   - const tempKey = `distribute_order_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
465   - // ② 存储完整item到本地缓存(同步存储,确保立即生效)
466   - try {
467   - uni.setStorageSync(tempKey, item);
468   - } catch (error) {
469   - console.error('存储分配工单数据失败:', error);
470   - uni.showToast({title: '数据存储异常,无法跳转', icon: 'none'});
471   - return;
472   - }
473   - // ③ URL仅传递临时key,无其他冗余参数
474   - uni.navigateTo({
475   - url: `/pages-sub/problem/regional-order-manage/distribution-order?tempKey=${tempKey}`
476   - })
  459 + // 大区经理分配
  460 + if (stepName === '大区经理分配') {
  461 + const tempKey = setOrderStorage(item, 'distribute_order');
  462 + tempKey && navToOrderPage('/pages-sub/problem/regional-order-manage/distribution-order', { tempKey });
477 463 }
478   - if (nextStepMap[item.taskKey]?.name == '督察员单子大区经理分配') {
479   - let postData = {
480   - "taskKey":item.taskKey,
481   - "taskId": item.taskId,
482   - "operateType":60,
483   - "workerDataId":item.id,
484   - "agree":0,
485   - "reason":item.remark,
486   - "roadId":item.roadId,
487   - "roadName":item.roadName,
488   - "pressingType":item.pressingType,
489   - "orderName":item.orderName,
490   - "expectedFinishDate": item.expectedFinishDate,
491   - "busiLine":item.busiLine,
492   - }
493   - const res = await dcyUniversalApproval(postData);
494   - uni.showToast({title: '分配成功', icon: 'success', duration: 1000});
495   - paging.value?.reload(); // 刷新列表
  464 + // 督察员单子大区经理分配
  465 + else if (stepName === '督察员单子大区经理分配') {
  466 + const postData = { taskKey: item.taskKey, taskId: item.taskId, operateType:60, workerDataId:item.id, agree:0, reason:item.remark, roadId:item.roadId, roadName:item.roadName, pressingType:item.pressingType, orderName:item.orderName, expectedFinishDate: item.expectedFinishDate, busiLine:item.busiLine };
  467 + resData = await dcyUniversalApproval(postData);
  468 + uni.showToast({title: '分配成功', icon: 'success'});
  469 + refreshOrderList();
496 470 }
497   -
498   -
499   - // 验收 - 打开弹窗
500   - if (nextStepMap[item.taskKey]?.name == '巡查员验收' || nextStepMap[item.taskKey]?.name == '养护组长验收') {
501   - currentAcceptItem.value = item; // 存储当前工单信息
502   - acceptReason.value = ''; // 清空上次的验收原因
503   - acceptRadioValue.value = '0'; // 重置默认选中“通过”
504   - acceptModalShow.value = true; // 显示验收弹窗
  471 + // 验收弹窗
  472 + else if (['巡查员验收', '养护组长验收'].includes(stepName)) {
  473 + currentAcceptItem.value = item;
  474 + acceptReason.value = '';
  475 + acceptRadioValue.value = '0';
  476 + acceptModalShow.value = true;
505 477 }
506   -
507   - // 发起人确认
508   - if (nextStepMap[item.taskKey]?.name == '发起人确认') {
509   - console.log(item)
  478 + // 发起人确认-结束工单
  479 + else if (stepName === '发起人确认') {
510 480 uni.showModal({
511 481 title: "结束工单",
512 482 content: "请确定是否结束工单?",
513   - success: async function (res) {
  483 + success: async (res) => {
514 484 if (res.confirm) {
515   - // 构建请求参数
516 485 const requestData = {
517   -
518   - "workerDataId": item.id,
519   - "taskKey": 'ylInspectorStart',
520   - "taskId": item.taskId,
521   - "operateType": 200,
522   - "agree": 1,
523   - "reason": '结束工单'
  486 + workerDataId: item.id, taskKey: 'ylInspectorStart', taskId: item.taskId,
  487 + operateType: 200, agree: 1, reason: '结束工单'
524 488 };
525   - // 调用回退工单接口 daquUniversalApproval
526   -
527   - if( userStore.userInfo.roles.includes('regional_manager')){ // 大区经理
528   - await daquUniversalApproval(requestData)
529   - }
530   - if( userStore.userInfo.roles.includes('Inspector_global')){ // 督察员
531   - await dcyUniversalApproval(requestData);
532   - }
533   - if( userStore.userInfo.roles.includes('patrol_global')){ // 全域巡查员
534   - await qyUniversalApproval(requestData);
535   - }
536   -
537   - uni.showToast({title: '结束成功', icon: 'success', duration: 1000});
538   - rejectModalShow.value = false;
539   - paging.value?.reload(); // 刷新列表
540   - } else if (res.cancel) {
541   - console.log("用户点击取消");
  489 + await callApprovalApi(requestData, item.taskKey);
  490 + uni.showToast({title: '结束成功', icon: 'success'});
  491 + refreshOrderList();
542 492 }
543   - },
  493 + }
544 494 });
545 495 }
546 496 } catch (error) {
547 497 console.error('处理工单失败:', error);
548   - uni.showToast({title: '处理失败,请重试', icon: 'none'});
  498 + uni.showToast({title: resData.msg, icon: 'none'});
549 499 }
550 500 };
551   -// 待办-回退工单(打开回退modal)
  501 +
  502 +// 回退工单-打开弹窗
552 503 const handleReject = (item) => {
553   - console.log('123213')
554   - // 校验工单有效性
555   - if (!item || !item.id) {
556   - uni.showToast({title: '工单信息异常,无法回退', icon: 'none'});
557   - return;
558   - }
  504 + if (!item?.id) return uni.showToast({title: '工单信息异常,无法回退', icon: 'none'});
559 505 currentRejectItem.value = item;
560   - rejectReason.value = ''; // 清空上次输入
561   - rejectImgs.clearImgs(); // 改造后:使用组合式函数的清空方法
562   - rejectModalShow.value = true; // 显示回退modal
  506 + rejectReason.value = '';
  507 + rejectImgs.clearImgs();
  508 + rejectModalShow.value = true;
563 509 };
564   -// 回退modal - 取消按钮
  510 +
  511 +// 关闭回退弹窗-重置状态
565 512 const handleRejectModalCancel = () => {
566 513 rejectModalShow.value = false;
567 514 rejectReason.value = '';
568   - rejectImgs.clearImgs(); // 改造后:使用组合式函数的清空方法
  515 + rejectImgs.clearImgs();
569 516 };
  517 +
570 518 // 确认回退工单
571 519 const confirmReject = async () => {
572   - // 严格校验回退原因(去除首尾空格)
573   - const rejectReasonTrim = rejectReason.value.trim();
574   - if (!rejectReasonTrim) {
575   - uni.showToast({title: '请填写回退原因', icon: 'none', duration: 1000});
576   - return;
577   - }
578   - // 校验当前工单有效性
579   - if (!currentRejectItem.value || !currentRejectItem.value.id) {
580   - uni.showToast({title: '工单信息异常,无法提交', icon: 'none', duration: 1000});
581   - rejectModalShow.value = false;
582   - return;
583   - }
  520 + const reason = rejectReason.value.trim();
  521 + if (!reason) return uni.showToast({title: '请填写回退原因', icon: 'none'});
  522 + const item = currentRejectItem.value;
  523 + if (!item?.id) return uni.showToast({title: '工单信息异常', icon: 'none'});
  524 +
  525 + uni.showLoading({title: '提交中...', mask: true});
584 526 try {
585   - // 显示加载中,防止重复提交
586   - uni.showLoading({title: '提交中...', mask: true});
587   - // 构建请求参数
588 527 const requestData = {
589   - "returnImgs": rejectImgs.getSuccessImgUrls(), // 改造后:获取上传成功的图片URL数组
590   - "workerDataId": currentRejectItem.value.id,
591   - "taskKey": currentRejectItem.value.taskKey,
592   - "taskId": currentRejectItem.value.taskId,
593   -
594   - "operateType": nextStepMap[currentRejectItem.value.taskKey].operateTypeNoPass,
595   - "agree": 1,
596   - "reason": rejectReasonTrim
  528 + returnImgs: rejectImgs.getSuccessImgUrls(), workerDataId: item.id, taskKey: item.taskKey,
  529 + taskId: item.taskId, operateType: nextStepMap[item.taskKey].operateTypeNoPass, agree:1, reason
597 530 };
598   - // 调用回退工单接口
599   - if(currentRejectItem.value.taskKey=='shRegionManager'){ // 对督察员单子 回退
600   - const res = await dcyUniversalApproval(requestData);
601   - }
602   - if(currentRejectItem.value.taskKey=='regionManager'){ // 对全域巡查员单子 回退
603   - const res = await qyUniversalApproval(requestData);
604   - }
605   - // 对大区经理单子 回退
606   - uni.showToast({title: '回退成功', icon: 'success', duration: 1000});
607   - rejectModalShow.value = false;
608   - paging.value?.reload(); // 刷新列表
  531 + await callApprovalApi(requestData, item.taskKey);
  532 + uni.showToast({title: '回退成功', icon: 'success'});
  533 + handleRejectModalCancel();
  534 + refreshOrderList();
609 535 } catch (error) {
610 536 console.error('回退工单失败:', error);
611   - uni.showToast({title: '网络异常,回退失败', icon: 'none', duration: 1000});
  537 + uni.showToast({title: '回退失败,请重试', icon: 'none'});
612 538 } finally {
613   - // 隐藏加载中
614 539 uni.hideLoading();
615 540 }
616 541 };
  542 +
617 543 // 新增工单
618 544 const handleAddOrder = () => {
619   - if (userStore.userInfo?.roles.includes('patrol_global')) { // 全域巡查员
620   - uni.navigateTo({
621   - url: '/pages-sub/problem/regional-order-manage/add-patrol-order'
622   - });
623   - }
624   - if (userStore.userInfo?.roles.includes('regional_manager')) { // 大区经理
625   - uni.navigateTo({
626   - url: '/pages-sub/problem/regional-order-manage/add-order'
627   - });
628   - }
629   - if (userStore.userInfo?.roles.includes('Inspector_global')) { // 督察员
630   - uni.navigateTo({
631   - url: '/pages-sub/problem/regional-order-manage/add-order'
632   - });
633   - }
  545 + const pageUrl = USER_ROLES.includes('patrol_global')
  546 + ? '/pages-sub/problem/regional-order-manage/add-patrol-order'
  547 + : '/pages-sub/problem/regional-order-manage/add-order';
  548 + navToOrderPage(pageUrl);
634 549 };
635   -// 验收弹窗 - 取消按钮(清空状态)
  550 +
  551 +// 关闭验收弹窗-重置状态
636 552 const handleAcceptModalCancel = () => {
637 553 acceptModalShow.value = false;
638   - acceptReason.value = ''; // 清空验收原因
639   - acceptRadioValue.value = '0'; // 重置单选框为“通过”
640   - acceptImgs.clearImgs(); // 清空验收图片
  554 + acceptReason.value = '';
  555 + acceptRadioValue.value = '0';
  556 + acceptImgs.clearImgs();
641 557 };
642   -// 验收弹窗 - 确定按钮(含returnImgs传参)
  558 +
  559 +// 验收提交
643 560 const handleAcceptModalConfirm = async () => {
644   - // 1. 校验验收原因是否为空
645   - if (!acceptReason.value.trim()) {
646   - uni.showToast({title: '请填写验收原因', icon: 'none', duration: 1000});
647   - return;
648   - }
649   - // 2. 校验验收原因长度
650   - if (acceptReason.value.length > 200) {
651   - uni.showToast({title: '验收原因最多200字', icon: 'none', duration: 1000});
652   - return;
653   - }
654   - try {
655   - // 3. 构建请求参数(含returnImgs)
656   - console.log(currentAcceptItem.value)
657   - console.log( userStore.userInfo.roles)
658   - console.log( userStore.userInfo.roles.includes('Inspector_global'))
659   - //
660   - if( userStore.userInfo.roles.includes('regional_manager')){ // 大区经理验收
661   - let postData = {
662   - "returnImgs": acceptImgs.getSuccessImgUrls(), // 验收图片URL数组
663   - "taskKey": currentAcceptItem.value.taskKey,
664   - "workerDataId": currentAcceptItem.value.id,
665   - "taskId": currentAcceptItem.value.taskId,
666   - "operateType": acceptRadioValue.value == 0 ? nextStepMap[currentAcceptItem.value.taskKey].operateTypePass : nextStepMap[currentAcceptItem.value.taskKey].operateTypeNoPass,
667   - "reason": acceptReason.value.trim(),
668   - "agree":acceptRadioValue.value,
669   - }
670   - await daquUniversalApproval(postData);
671   - }
672   - //
673   - if( userStore.userInfo.roles.includes('patrol_global')){ // 全域巡查员验收
674   - let postData = {
675   - "returnImgs": acceptImgs.getSuccessImgUrls(), // 验收图片URL数组
676   - "taskKey": currentAcceptItem.value.taskKey,
677   - "taskId": currentAcceptItem.value.taskId,
678   - "workerDataId": currentAcceptItem.value.id,
679   - "operateType": acceptRadioValue.value == 0 ? nextStepMap[currentAcceptItem.value.taskKey].operateTypePass : nextStepMap[currentAcceptItem.value.taskKey].operateTypeNoPass,
680   - "reason": acceptReason.value.trim(),
681   - "agree": acceptRadioValue.value
682   - }
683   - await qyUniversalApproval(postData);
684   - }
685   - //
686   - if( userStore.userInfo.roles.includes('Inspector_global')){ // 督察员验收
687   - let postData = {
688   - "returnImgs": acceptImgs.getSuccessImgUrls(), // 验收图片URL数组
689   - "taskKey": currentAcceptItem.value.taskKey,
690   - "taskId": currentAcceptItem.value.taskId,
691   - "workerDataId": currentAcceptItem.value.id,
692   - "operateType": acceptRadioValue.value == 0 ? nextStepMap[currentAcceptItem.value.taskKey].operateTypePass : nextStepMap[currentAcceptItem.value.taskKey].operateTypeNoPass,
693   - "reason": acceptReason.value.trim(),
694   - "agree": acceptRadioValue.value
695   - }
696   - await dcyUniversalApproval(postData);
697   - }
  561 + const reason = acceptReason.value.trim();
  562 + if (!reason) return uni.showToast({title: '请填写验收原因', icon: 'none'});
  563 + if (reason.length > 200) return uni.showToast({title: '验收原因最多200字', icon: 'none'});
  564 +
  565 + const item = currentAcceptItem.value;
  566 + if (!item?.id) return uni.showToast({title: '工单信息异常', icon: 'none'});
698 567  
699   - // 4. 操作成功处理
700   - uni.showToast({title: '提交成功', icon: 'success', duration: 1000});
701   - handleAcceptModalCancel(); // 清空状态
702   - paging.value?.reload(); // 刷新工单列表
  568 + try {
  569 + const postData = {
  570 + returnImgs: acceptImgs.getSuccessImgUrls(), taskKey: item.taskKey, workerDataId: item.id,
  571 + taskId: item.taskId, agree: acceptRadioValue.value, reason,
  572 + operateType: acceptRadioValue.value === '0' ? nextStepMap[item.taskKey].operateTypePass : nextStepMap[item.taskKey].operateTypeNoPass
  573 + };
  574 + await callApprovalApi(postData, item.taskKey);
  575 + uni.showToast({title: '提交成功', icon: 'success'});
  576 + handleAcceptModalCancel();
  577 + refreshOrderList();
703 578 } catch (error) {
704   - // 5. 操作失败处理
705 579 console.error('验收失败:', error);
706   - uni.showToast({title: '验收提交失败,请重试', icon: 'none', duration: 1000});
  580 + uni.showToast({title: '验收提交失败,请重试', icon: 'none'});
707 581 }
708 582 };
709   -// 页面初始化
  583 +
  584 +// ========== 页面生命周期 ==========
710 585 onLoad(() => {
711   - // 初始化加载列表
712   - paging.value?.reload();
  586 + refreshOrderList();
713 587 });
714 588 </script>
715 589  
... ... @@ -734,15 +608,10 @@ onLoad(() =&gt; {
734 608 .select-wrap {
735 609 width: 120rpx;
736 610 margin-right: 20rpx;
737   -
738   - :deep(.u-select) {
  611 + :deep(.u-select), :deep(.u-input__placeholder) {
739 612 width: 100%;
740 613 font-size: 28rpx;
741 614 }
742   -
743   - :deep(.u-input__placeholder) {
744   - font-size: 28rpx;
745   - }
746 615 }
747 616  
748 617 .search-input-wrap {
... ... @@ -758,31 +627,14 @@ onLoad(() =&gt; {
758 627 box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
759 628 }
760 629  
761   -.card-body {
762   -
763   -}
764   -
765   -// 回退modal样式
766   -.reject-modal-content {
  630 +// 弹窗公共样式
  631 +.reject-modal-content, .accept-modal-content {
767 632 width: 100%;
768 633 box-sizing: border-box;
769 634 padding: 10rpx 0;
770 635 }
771 636  
772   -.textarea-label {
773   - font-size: 28rpx;
774   - color: #333;
775   - margin-bottom: 10rpx;
776   -
777   - .required-mark {
778   - color: #f56c6c;
779   - margin-left: 4rpx;
780   - }
781   -}
782   -
783 637 .upload-wrap {
784   - margin-top: 20rpx;
785   -
786 638 .upload-title {
787 639 font-size: 28rpx;
788 640 color: #333;
... ... @@ -790,37 +642,20 @@ onLoad(() =&gt; {
790 642 }
791 643 }
792 644  
793   -.mt-20 {
794   - margin-top: 20rpx;
795   -}
796   -
797   -.mt-30 {
798   - margin-top: 30rpx;
799   -}
800   -
801   -// 养护组长验收弹窗样式
802   -.accept-modal-content {
803   - width: 100%;
804   - box-sizing: border-box;
805   -}
806   -
  645 +// 验收弹窗单选框样式
807 646 .radio-group-wrap {
808 647 display: flex;
809 648 align-items: center;
810   - gap: 40rpx; // 单选框之间的间距
  649 + gap: 40rpx;
811 650 font-size: 28rpx;
812 651 margin-bottom: 20rpx;
813 652 }
814 653  
815 654 .textarea-wrap {
816 655 width: 100%;
817   - margin-top: 30rpx;
818 656 }
819 657  
820   -.modal-btn-wrap {
821   - display: flex;
822   - align-items: center;
823   - justify-content: flex-end;
824   - padding-right: 10rpx;
825   -}
  658 +// 间距公共样式
  659 +.mt-20 { margin-top: 20rpx; }
  660 +.mt-30 { margin-top: 30rpx; }
826 661 </style>
827 662 \ No newline at end of file
... ...
pages-sub/problem/regional-order-manage/order-detail.vue
... ... @@ -267,23 +267,23 @@
267 267 </view>
268 268  
269 269 <!-- activeTab==0的时候才出现, 也就是待办 -->
270   - <view v-if="activeTab==0&&nextStepMap[orderDetail.taskKey]" class="fixed-bottom-btn-wrap">
271   - <view class="u-body-item u-flex common-justify-between common-item-center ">
272   - <up-button type="warning" size="normal" @click="handleReject(orderDetail)"
273   - v-show="nextStepMap[orderDetail.taskKey].backShow">回退
274   - </up-button>
275   -
276   - <up-button type="success" size="normal" @click="handleRenew(orderDetail)"
277   - v-show="nextStepMap[orderDetail.taskKey].renewShow">重新提交
278   - </up-button>
279   -
280   - <up-button type="primary" size="normal" @click="handleProcess(orderDetail)">{{
281   - nextStepMap[orderDetail.taskKey].btnText
282   - }}
283   - </up-button>
284   -
285   - </view>
286   - </view>
  270 +<!-- <view v-if="activeTab==0&&nextStepMap[orderDetail.taskKey]" class="fixed-bottom-btn-wrap">-->
  271 +<!-- <view class="u-body-item u-flex common-justify-between common-item-center ">-->
  272 +<!-- <up-button type="warning" size="normal" @click="handleReject(orderDetail)"-->
  273 +<!-- v-show="nextStepMap[orderDetail.taskKey].backShow">回退-->
  274 +<!-- </up-button>-->
  275 +
  276 +<!-- <up-button type="success" size="normal" @click="handleRenew(orderDetail)"-->
  277 +<!-- v-show="nextStepMap[orderDetail.taskKey].renewShow">重新提交-->
  278 +<!-- </up-button>-->
  279 +
  280 +<!-- <up-button type="primary" size="normal" @click="handleProcess(orderDetail)">{{-->
  281 +<!-- nextStepMap[orderDetail.taskKey].btnText-->
  282 +<!-- }}-->
  283 +<!-- </up-button>-->
  284 +
  285 +<!-- </view>-->
  286 +<!-- </view>-->
287 287  
288 288 <!-- 回退原因弹窗(新增图片上传) -->
289 289 <up-modal
... ...
pages-sub/problem/work-order-manage/add-maintain-order.vue
... ... @@ -492,11 +492,11 @@ const loadCoProcessorList = async () =&gt; {
492 492 }).filter(Boolean); // 过滤null项
493 493 } else {
494 494 coProcessorList.value = []
495   - uni.showToast({
496   - title: '暂无共同处理人数据',
497   - icon: 'none',
498   - duration: 2000
499   - });
  495 + // uni.showToast({
  496 + // title: '暂无共同处理人数据',
  497 + // icon: 'none',
  498 + // duration: 1000
  499 + // });
500 500 console.warn('loadCoProcessorList提示:处理人列表为空');
501 501 }
502 502 } catch (err) {
... ...
pages.json
... ... @@ -10,7 +10,8 @@
10 10 {
11 11 "path": "pages/login/index",
12 12 "style": {
13   - "navigationBarTitleText": "登录"
  13 + "navigationBarTitleText": "登录",
  14 + "navigationStyle": "custom"
14 15 }
15 16 },
16 17 {
... ... @@ -166,7 +167,7 @@
166 167  
167 168 {
168 169 "path": "regional-order-manage/add-patrol-order",
169   - "style": { "navigationBarTitleText": "待派单" }
  170 + "style": { "navigationBarTitleText": "提交工单" }
170 171 },
171 172  
172 173 {
... ... @@ -197,7 +198,21 @@
197 198 {
198 199 "path": "tree-archive/index",
199 200 "style": { "navigationBarTitleText": "行道树档案" }
  201 + },
  202 + {
  203 + "path": "tree-archive/treeRecord",
  204 + "style": { "navigationBarTitleText": "行道树记录" }
  205 + },
  206 + {
  207 + "path": "tree-archive/addTree",
  208 + "style": { "navigationBarTitleText": "新增行道树" }
  209 + },
  210 + {
  211 + "path": "tree-archive/editTree",
  212 + "style": { "navigationBarTitleText": "编辑行道树" }
200 213 }
  214 +
  215 +
201 216 ]
202 217 }
203 218 ],
... ...
pages/login/index.vue
1 1 <template>
2 2 <view class="login-page">
3   - <!-- 纯CSS渐变动效背景(替代粒子动画,兼容所有版本) -->
4   - <view class="bg-animation"></view>
  3 + <!-- 顶部标题区 - 加高 + 左下大圆角 + 底部蓝色过滤阴影 + 从浅到深 蓝色渐变背景 -->
  4 + <view class="top-title">
  5 + <text class="welcome-text">你好,欢迎光临</text>
  6 + <text class="platform-name">全域智能运营管理平台</text>
  7 + </view>
5 8  
6   - <!-- 登录表单区域(悬浮层) -->
  9 + <!-- 登录表单区域 - 往上偏移 盖住top-title一部分 -->
7 10 <view class="login-form">
8   - <!-- 登录标题(简约大气) -->
9   - <view class="login-title">园林登录</view>
  11 + <!-- 登录标题 -->
  12 + <view class="login-title">账户登录</view>
10 13  
11   - <!-- 账号输入框 -->
12   - <view class="form-item">
13   - <up-input
14   - v-model="form.account"
15   - placeholder="请输入登录账号"
16   - border="surround"
17   - clearable
18   - maxlength="30"
19   - input-align="left"
20   - :disabled="isLoading"
21   - @blur="checkAccount"
22   - :custom-style="{
23   - backgroundColor: 'rgba(255, 255, 255, 0.9)',
24   - borderColor: '#e5e7eb'
25   - }"
  14 + <!-- ✅ 核心:和工单页同款 up-form 表单校验容器 -->
  15 + <up-form
  16 + label-position="left"
  17 + :model="form"
  18 + ref="loginFormRef"
  19 + labelWidth="0"
  20 + >
  21 + <!-- 账号输入框 + 校验 -->
  22 + <up-form-item
  23 + prop="account"
26 24 >
27   - <template #prefix>
28   - <up-icon name="account" color="#6b7280" size="20"></up-icon>
29   - </template>
30   - </up-input>
31   - <!-- 账号错误提示 -->
32   - <view class="error-tip" v-if="error.account">{{ error.account }}</view>
33   - </view>
  25 + <up-input
  26 + v-model="form.account"
  27 + border="surround"
  28 + clearable
  29 + maxlength="30"
  30 + input-align="left"
  31 + fontSize="16px"
  32 + :disabled="isLoading"
  33 + shape="circle"
  34 + placeholder="请输入账户"
  35 + selectionStart="15"
  36 + selectionEnd="15"
  37 + @blur="() => loginFormRef.validateField('account')"
  38 + />
  39 + </up-form-item>
34 40  
35   - <!-- 密码输入框(移除查看密码功能) -->
36   - <view class="form-item">
37   - <up-input
38   - v-model="form.password"
39   - placeholder="请输入登录密码"
40   - maxlength="20"
41   - border="surround"
42   - clearable
43   - input-align="left"
44   - type="password"
45   - :disabled="isLoading"
46   - @blur="checkPassword"
47   - :custom-style="{
48   - backgroundColor: 'rgba(255, 255, 255, 0.9)',
49   - borderColor: '#e5e7eb'
50   - }"
  41 + <!-- 密码输入框 + 校验 -->
  42 + <up-form-item
  43 + prop="password"
51 44 >
52   - <template #prefix>
53   - <up-icon name="lock" color="#6b7280" size="20"></up-icon>
54   - </template>
55   - </up-input>
56   - <!-- 密码错误提示 -->
57   - <view class="error-tip" v-if="error.password">{{ error.password }}</view>
58   - </view>
  45 + <up-input
  46 + v-model="form.password"
  47 + placeholder="请输入密码"
  48 + maxlength="20"
  49 + border="surround"
  50 + clearable
  51 + input-align="left"
  52 + type="password"
  53 + :disabled="isLoading"
  54 + shape="circle"
  55 + selectionStart="5"
  56 + selectionEnd="5"
  57 + @blur="() => loginFormRef.validateField('password')"
  58 + />
  59 + </up-form-item>
  60 + </up-form>
59 61  
60 62 <!-- 登录按钮 -->
61 63 <up-button
... ... @@ -64,44 +66,58 @@
64 66 size="large"
65 67 :loading="isLoading"
66 68 @click="handleLogin"
67   - :custom-style="{
68   -
69   - }"
  69 + shape="circle"
70 70 >
71 71 登录
72 72 </up-button>
73 73 </view>
  74 +
  75 + <!-- 版权信息 -->
  76 + <view class="copyright">蓟城山水集团版权所有</view>
74 77 </view>
75 78 </template>
76 79  
77 80 <script setup>
78   -import { ref, reactive, onMounted } from 'vue';
  81 +import { ref, reactive, onMounted, nextTick } from 'vue';
79 82 import { useUserStore } from '@/pinia/user';
80 83 import globalConfig from '@/common/config/global';
81 84  
82   -// 表单数据
  85 +// ========== 【全局实例 & 基础状态】 和工单页写法一致 ==========
  86 +const userStore = useUserStore();
  87 +const loginFormRef = ref(null); // 表单ref 用于校验
  88 +const isLoading = ref(false); // 登录加载状态
  89 +
  90 +// ========== 【表单数据】 和工单页一致的 reactive 声明 ==========
83 91 const form = reactive({
84   - account: '', // 账号
85   - password: '' // 密码
  92 + account: '', // 账号
  93 + password: '' // 密码
86 94 });
87 95  
88   -// 状态管理
89   -const isLoading = ref(false);
90   -const error = reactive({
91   - account: '',
92   - password: ''
  96 +// ========== 【表单校验规则】 ✅核心 和工单页1:1同款校验规则写法 ==========
  97 +const loginFormRules = reactive({
  98 + // 账号校验:必填 + 长度限制 2-30位(合理账号长度,可自行调整)
  99 + account: [
  100 + { type: 'string', required: true, message: '请输入登录账号', trigger: ['change', 'blur'] },
  101 + { type: 'string', min: 2, max: 30, message: '账号长度为2-30个字符', trigger: ['change', 'blur'] }
  102 + ],
  103 + // 密码校验:必填 + 长度限制 6-20位(行业通用密码长度,可自行调整)
  104 + password: [
  105 + { type: 'string', required: true, message: '请输入登录密码', trigger: ['change', 'blur'] },
  106 + { type: 'string', min: 6, max: 20, message: '密码长度为6-20个字符', trigger: ['change', 'blur'] }
  107 + ]
93 108 });
94 109  
95   -// 实例化 Pinia 用户仓库
96   -const userStore = useUserStore();
97   -
98   -// 页面加载时初始化
  110 +// ========== 【生命周期】 nextTick 设置校验规则 和工单页一致 ==========
99 111 onMounted(() => {
100 112 // 检查登录态
101 113 checkLoginStatus();
  114 + // nextTick确保表单挂载完成再赋值规则,修复校验不生效bug,和工单页写法一致
  115 + nextTick(() => {
  116 + loginFormRef.value?.setRules(loginFormRules);
  117 + });
102 118 });
103 119  
104   -// 检查登录状态
  120 +// ========== 【检查登录状态】 原有逻辑不变 ==========
105 121 const checkLoginStatus = () => {
106 122 try {
107 123 // 已登录则直接跳首页
... ... @@ -109,7 +125,6 @@ const checkLoginStatus = () =&gt; {
109 125 uni.switchTab({
110 126 url: '/pages/workbench/index',
111 127 fail: () => {
112   - // 非tabBar页面用redirectTo
113 128 uni.reLaunch({ url: '/pages/workbench/index' });
114 129 }
115 130 });
... ... @@ -120,38 +135,14 @@ const checkLoginStatus = () =&gt; {
120 135 }
121 136 };
122 137  
123   -// 校验账号
124   -const checkAccount = () => {
125   - if (!form.account) {
126   - error.account = '请输入登录账号';
127   - } else {
128   - error.account = '';
129   - }
130   -};
131   -
132   -// 校验密码
133   -const checkPassword = () => {
134   - if (!form.password) {
135   - error.password = '请输入登录密码';
136   - } else {
137   - error.password = '';
138   - }
139   -};
140   -
141   -// 表单整体校验
142   -const validateForm = () => {
143   - checkAccount();
144   - checkPassword();
145   - return !error.account && !error.password;
146   -};
147   -
148   -// 登录处理
  138 +// ========== 【核心登录方法】 ✅ 完整表单校验 + 登录逻辑,和工单页submit提交逻辑一致 ==========
149 139 const handleLogin = async () => {
150   - if (!validateForm()) return;
151   -
152   - isLoading.value = true;
153   -
154 140 try {
  141 + // ✅ 第一步:先执行表单整体校验,通过后再执行登录逻辑(和工单页submitWorkOrder写法一致)
  142 + await loginFormRef.value.validate();
  143 +
  144 + isLoading.value = true;
  145 + // 执行登录请求
155 146 await userStore.login({
156 147 username: form.account,
157 148 password: form.password
... ... @@ -170,16 +161,15 @@ const handleLogin = async () =&gt; {
170 161 });
171 162 }, 1000);
172 163 } catch (err) {
173   - console.error('登录失败详情:', err);
174   - const errorMsg =
175   - err.message === '网络异常,请稍后重试'
176   - ? '网络异常,请稍后重试'
177   - : err.message || '登录失败,请检查账号密码';
178   - uni.showToast({
179   - title: errorMsg,
180   - icon: 'none',
181   - duration: 2000
182   - });
  164 + // ✅ 校验失败/登录失败 统一捕获提示,和工单页异常处理一致
  165 + if (!Array.isArray(err)) {
  166 + console.error('登录失败:', err);
  167 + uni.showToast({
  168 + title: err.message || '账号或密码错误,请重试',
  169 + icon: 'none',
  170 + duration: 2000
  171 + });
  172 + }
183 173 } finally {
184 174 isLoading.value = false;
185 175 }
... ... @@ -190,122 +180,83 @@ const handleLogin = async () =&gt; {
190 180 // 核心布局
191 181 .login-page {
192 182 min-height: 100vh;
193   - position: relative;
194   - padding: 40rpx 30rpx;
  183 + padding: 0;
195 184 box-sizing: border-box;
196   - overflow: hidden; // 隐藏动效溢出
197   -
198   - // 适配小程序安全区
199   - /* #ifdef MP-WEIXIN */
200   - padding-bottom: constant(safe-area-inset-bottom);
201   - padding-bottom: env(safe-area-inset-bottom);
202   - /* #endif */
  185 + overflow: hidden;
  186 + position: relative;
  187 + background: #f5f7fa;
203 188 }
204 189  
205   -// 纯CSS渐变动效背景(替代粒子,兼容所有版本)
206   -.bg-animation {
207   - position: absolute;
208   - top: 0;
209   - left: 0;
210   - width: 100%;
211   - height: 100%;
212   - z-index: 0;
213   - // 基础渐变底色
214   - background: linear-gradient(120deg, #f0f9ff 0%, #e6f7ff 100%);
215   - // 伪粒子动效(多个透明渐变圆缓慢移动)
216   - &::before,
217   - &::after {
218   - content: '';
219   - position: absolute;
220   - border-radius: 50%;
221   - background: rgba(59, 130, 246, 0.05);
222   - animation: float 20s infinite linear;
223   - }
224   -
225   - &::before {
226   - width: 600rpx;
227   - height: 600rpx;
228   - top: -200rpx;
229   - left: -200rpx;
230   - }
  190 +// 顶部:从左到右 由浅到深 线性渐变 过渡自然柔和 无突兀色块
  191 +.top-title {
  192 + background: linear-gradient(120deg, #4299e1 0%, #2970e8 50%, #2563eb 100%);
  193 + color: #fff;
  194 + padding: 240rpx 30rpx 80rpx;
  195 + text-align: left;
  196 + border-bottom-left-radius: 120rpx;
  197 + position: relative;
  198 + z-index: 1;
  199 + box-shadow: 0 25rpx 40rpx -15rpx rgba(37, 99, 235, 0.3);
231 200  
232   - &::after {
233   - width: 800rpx;
234   - height: 800rpx;
235   - bottom: -300rpx;
236   - right: -300rpx;
237   - animation-delay: -10s; // 错开动画时间
  201 + .welcome-text {
  202 + font-size: 46rpx;
  203 + display: block;
  204 + margin-bottom: 10px;
  205 + font-weight: 500;
238 206 }
239 207  
240   - // 额外小动效点(模拟粒子)
241   - & > view {
242   - position: absolute;
243   - width: 400rpx;
244   - height: 400rpx;
245   - border-radius: 50%;
246   - background: rgba(59, 130, 246, 0.03);
247   - top: 50%;
248   - left: 50%;
249   - transform: translate(-50%, -50%);
250   - animation: float 15s infinite linear reverse;
  208 + .platform-name {
  209 + margin-bottom: 10px;
  210 + font-size: 46rpx;
  211 + display: block;
  212 + opacity: 0.95;
251 213 }
252 214 }
253 215  
254   -// 浮动动画(模拟粒子运动)
255   -@keyframes float {
256   - 0% {
257   - transform: translate(0, 0) rotate(0deg);
258   - }
259   - 25% {
260   - transform: translate(50rpx, 30rpx) rotate(90deg);
261   - }
262   - 50% {
263   - transform: translate(0, 60rpx) rotate(180deg);
264   - }
265   - 75% {
266   - transform: translate(-50rpx, 30rpx) rotate(270deg);
267   - }
268   - 100% {
269   - transform: translate(0, 0) rotate(360deg);
270   - }
271   -}
272   -
273   -// 登录表单(悬浮层)
274 216 .login-form {
  217 + background-color: #fff;
  218 + padding: 40rpx 30rpx;
  219 + border-radius: 16rpx;
  220 + box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
  221 + margin: -60rpx 40rpx 0;
275 222 position: relative;
276 223 z-index: 10;
277   - background-color: rgba(255, 255, 255, 0.95);
278   - padding: 60rpx 40rpx;
279   - border-radius: 16rpx;
280   - box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
281   - max-width: 600rpx;
282   - margin: 0 auto;
283   - margin-top: 100rpx;
  224 + box-sizing: border-box;
284 225  
285   - // 登录标题
286 226 .login-title {
287   - font-size: 36rpx;
  227 + font-size: 38rpx;
288 228 font-weight: 600;
289   - color: #111827;
290   - text-align: center;
291   - margin-bottom: 40rpx;
292   - letter-spacing: 2rpx;
  229 + color: #666;
  230 + margin-bottom: 30rpx;
  231 + letter-spacing: 1rpx;
293 232 }
294 233 }
295 234  
296   -// 表单项
297   -.form-item {
298   - margin-bottom: 30rpx;
  235 +// 适配表单校验的间距
  236 +:deep(.u-form-item) {
  237 + margin-bottom: 20rpx;
299 238 position: relative;
  239 +}
300 240  
301   - // 错误提示
302   - .error-tip {
303   - font-size: 24rpx;
304   - color: #ef4444;
305   - margin-top: 10rpx;
306   - line-height: 1.2;
307   - padding-left: 10rpx;
308   - }
  241 +// 登录按钮:和顶部完全同款 从左到右 由浅到深 线性渐变 + 保留所有原尺寸
  242 +.login-btn {
  243 + margin-top: 40rpx;
  244 + width: 100%;
  245 + height: 88rpx;
  246 + line-height: 88rpx;
  247 + border-radius: 8rpx;
  248 + font-size: 32rpx;
  249 + background: linear-gradient(120deg, #4299e1 0%, #2970e8 50%, #2563eb 100%) !important;
  250 + border: none !important;
  251 + &::after { border: none !important; }
309 252 }
310 253  
  254 +.copyright {
  255 + width: 100%;
  256 + text-align: center;
  257 + font-size: 24rpx;
  258 + color: #999;
  259 + position: fixed;
  260 + bottom: 100px;
  261 +}
311 262 </style>
312 263 \ No newline at end of file
... ...
static/imgs/tree/tree-high.png 0 → 100644

1003 Bytes

static/imgs/tree/treearound.png 0 → 100644

927 Bytes