吉安感知网项目-前端
zhanghx
8 hours ago 4442c8e908d8ff01ed26fdd4ad0c68a29088e3e4
feat:设置记录
2 files modified
791 ■■■■■ changed files
uniapps/work-app/src/subPackages/workDetail/HandleWork/index.vue 5 ●●●● patch | view | raw | blame | history
uniapps/work-app/src/subPackages/workDetail/HandleWorking/index.vue 786 ●●●●● patch | view | raw | blame | history
uniapps/work-app/src/subPackages/workDetail/HandleWork/index.vue
@@ -172,12 +172,15 @@
// 上传处置按钮 - 移到滚动区域外面
.upload_handle {
    // #ifdef APP-PLUS
    padding-top: 60rpx;
    // #endif
    height: 80rpx; // 改大一点,方便点击
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding-right: 24rpx;
    margin: 60rpx 0 12rpx 0; // 上下间距
    margin: 0 0 12rpx 0; // 上下间距
    color: #1d6fe9;
    font-size: 28rpx;
    flex-shrink: 0; // 防止被压缩
uniapps/work-app/src/subPackages/workDetail/HandleWorking/index.vue
@@ -1,381 +1,473 @@
<script setup>
import { onLoad } from '@dcloudio/uni-app'
onLoad(options => {
    console.log(options, '9090')
    // eventId.value = options?.id || ''
    // eventStatus.value = options?.eventStatus || ''
})
</script>
<template>
    <div class="workDetailContainer">
        <!-- 自定义导航栏 -->
        <u-navbar title="工单详情" :is-back="true" back-text="" :back-icon-size="40" @left-click="onBackClick">
            <template #right>
                <div v-if="isH5" class="tapShare-icon"><img src="/static/images/work/share.svg" @click="onShareClick" /></div>
                <div v-else class="share-btn" @click="onShareClick"></div>
            </template>
        </u-navbar>
        <div class="detailTop">打发打发打发打发</div>
    </div>
  <view class="handle-page">
    <u-navbar title="待办处置" :is-back="true" back-text="" :back-icon-size="40" @left-click="onBackClick"></u-navbar>
    <scroll-view class="form-scroll" scroll-y>
      <view class="form-section description-section">
        <view class="section-header">
          <text class="section-title">处置描述</text>
        </view>
        <view class="description-wrapper">
                    <textarea
            v-model="formData.disposeContent"
            class="description-input"
            maxlength="140"
            placeholder="请输入"
            placeholder-class="description-placeholder"
            :show-confirm-bar="false"
          />
          <text class="word-count">{{ descriptionLength }}/140</text>
        </view>
      </view>
      <!--            <view class="form-section status-section">-->
      <!--                <text class="section-title">处置中</text>-->
      <!--                <switch class="status-switch" :checked="formData.isHandling" color="#00b578" @change="handleStatusChange" />-->
      <!--            </view>-->
      <view class="form-section upload-section">
        <view class="section-header upload-header">
          <text class="section-title">附件上传</text>
        </view>
        <!--                <button class="select-file-btn" @click="handleChooseFile">选择文件</button>-->
        <u-button color="#1D6FE9" class="select-file-btn" :disabled="uploading" @click="handleChooseFile">
          {{ uploading ? '上传中...' : '选择文件' }}
        </u-button>
        <view v-if="attachmentList.length" class="attachment-list">
          <view v-for="(file, index) in attachmentList" :key="`${file.path}-${index}`" class="attachment-item">
            <view class="file-info">
              <text class="file-name">{{ file.name }}</text>
              <text v-if="file.size" class="file-size">{{ formatFileSize(file.size) }}</text>
            </view>
            <text class="remove-file-btn" @click.stop="handleRemoveFile(index)">×</text>
          </view>
        </view>
      </view>
    </scroll-view>
    <view class="action-footer">
      <u-button color="#AEAEAE" class="custom-style" @click="handleCancel">取消</u-button>
      <u-button color="#1D6FE9" class="custom-style" :disabled="submitting || uploading" @click="handleSubmit">
        {{ submitting ? '提交中...' : '确定' }}
      </u-button>
    </view>
  </view>
</template>
<style lang="scss" scoped>
    .workDetailContainer {
        // 添加导航栏高度的padding-top,避免内容被遮挡
        padding-top: 88rpx;
        :deep() {
            .u-navbar__content {
                background: url('https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_app_assets/images/user/bg.png')
                no-repeat !important;
                background-size: 100% !important;
                height: 88rpx !important;
            }
        } // 分享按钮样式
        .share-btn {
            // #ifdef APP-PLUS
            width: 40rpx;
            height: 40rpx;
            background-image: url('/static/images/work/share.svg');
            background-size: contain;
            background-repeat: no-repeat;
            background-position: center;
            // #endif
        }
        .tapShare-icon {
            width: 40rpx;
            height: 40rpx;
            img {
                width: 40rpx;
                height: 40rpx;
            }
        }
        .detailTop {
            // #ifdef APP-PLUS
            padding-top: 60rpx;
            // #endif
            .image-container {
                position: relative;
                width: 100%;
                height: 410rpx;
<script setup>
import { computed, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { backGdApi, addDisposeRecord, uploadFileAPI } from '@/api/work/index.js'
                .detailImage {
                    width: 100%;
                    height: 100%;
                    display: block;
                    object-fit: cover;
                }
                // 轮播图容器
                :deep(.u-swiper) {
                    // 指示器容器 - 修改为居中对齐
                    .u-swiper__indicator {
                        position: absolute;
                        left: 50%;
                        transform: translateX(-50%);
                        bottom: 20rpx;
                        display: flex;
                        justify-content: center;
                        align-items: center;
                        z-index: 10;
                    }
                    .u-swiper-indicator__wrapper__dot--active {
                        width: 5px !important;
                    }
                }
            }
        }
const MAX_FILE_COUNT = 9
const eventId = ref('')
const eventStatus = ref(null)
const submitting = ref(false)
const uploading = ref(false)
const attachmentList = ref([])
const formData = ref({
  disposeContent: '',
  isHandling: true,
})
const navbarTitleStyle = {
  color: '#ffffff',
  fontSize: '34rpx',
  fontWeight: '500',
}
        .worderContainer {
            padding: 0 24rpx;
            padding-bottom: 4rpx;
            background: #f6f6f6;
            margin-top: 20rpx;
            height: calc(100vh - 530rpx);
            overflow: auto;
        }
        .workOrderContent {
            margin-top: 30epx;
            background-color: #fff;
            border-radius: 12rpx;
            padding: 20rpx;
            margin-bottom: 34rpx;
            .workOrderTitle {
                font-family: Source Han Sans CN, Source Han Sans CN;
                font-weight: bold;
                font-size: 32rpx;
                color: #222324;
            }
            .workOrderContainer {
                .orderRow {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    min-height: 96rpx;
                    border-bottom: 1px solid #f5f5f5;
                    color: #7b7b7b;
                    .rowTitle {
                        font-family: Source Han Sans CN, Source Han Sans CN;
                        font-weight: 400;
                        font-size: 30rpx;
                        color: #222324;
                        white-space: nowrap;
                    }
                    .rowAddress {
                        font-family: Source Han Sans CN, Source Han Sans CN;
                        font-weight: 400;
                        font-size: 28rpx;
                        color: #1d6fe9;
                        text-decoration: underline;
                        text-align: right;
// 计算处置描述字数
const descriptionLength = computed(() => formData.value.disposeContent.length)
                        padding-top: 1px;
                        padding-left: 5px;
                        padding-right: 2px;
                    }
                    .status-picker {
                        .picker-value {
                            font-family: Source Han Sans CN, Source Han Sans CN;
                            font-weight: 400;
                            font-size: 28rpx;
                            color: #1d6fe9;
                            display: flex;
                            align-items: center;
                            gap: 8rpx;
                        }
                        .picker-arrow {
                            font-size: 20rpx;
                            color: #999;
                        }
                    }
                }
            }
        }
// 读取当前工单标识
onLoad(options => {
  eventId.value = options?.id ?? ''
})
        .actionButton {
            display: flex;
            justify-content: center;
            align-items: center;
            box-sizing: border-box;
            width: 100%;
            height: 61px;
            padding: 0 24rpx;
// 返回上一页
function handleBack() {
  uni.navigateBack()
}
            border-radius: 6px 6px 6px 6px;
// 切换处置状态
// function handleStatusChange(event) {
//     formData.value.isHandling = event.detail.value
// }
            .btngroups {
                display: flex;
                align-items: center;
                width: 100%;
                min-width: 0;
                :deep(.u-button) {
                    width: 0 !important;
                    min-width: 0;
                    margin: 0 !important;
                    flex: 1;
                    height: 76rpx !important;
                }
                &:not(.three-actions) {
                    :deep(.u-button + .u-button) {
                        margin-left: 20rpx !important;
                    }
                }
                &.three-actions {
                    justify-content: center;
                    :deep(.u-button) {
                        width: 210rpx !important;
                        flex: 0 0 210rpx;
                    }
                    :deep(.u-button:last-child) {
                        margin-left: 20rpx !important;
                    }
                }
            }
        }
    }
// 提取文件名称
function getFileName(filePath = '') {
  const normalizedPath = filePath.replace(/\\/g, '/')
  return decodeURIComponent(normalizedPath.split('/').pop() || '未命名文件')
}
    .complete-modal-overlay {
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        z-index: 10000;
        display: flex;
        align-items: center;
        justify-content: center;
        box-sizing: border-box;
        padding: 40rpx;
        background: rgba(0, 0, 0, 0.55);
    }
// 上传单个已选择文件
async function uploadSelectedFile(file) {
  const response = await uploadFileAPI(file.path)
  const link = response?.data?.link || response?.data?.url || response?.link
  if (!link) throw new Error('上传接口未返回文件地址')
  return { ...file, link }
}
    .complete-modal {
        box-sizing: border-box;
        width: 620rpx;
        max-width: 100%;
        padding: 32rpx;
        background: #ffffff;
        border-radius: 12rpx;
    }
// 处理已选择文件
async function handleChooseSuccess(result) {
  const selectedFiles = result.tempFiles?.length
    ? result.tempFiles
    : (result.tempFilePaths ?? []).map(path => ({ path }))
  const availableCount = MAX_FILE_COUNT - attachmentList.value.length
  const normalizedFiles = selectedFiles
    .slice(0, availableCount)
    .map(file => {
      const path = file.path || file.tempFilePath || ''
      return {
        name: file.name || getFileName(path),
        path,
        size: Number(file.size) || 0,
      }
    })
    .filter(file => file.path)
  if (!normalizedFiles.length) return
    .complete-modal-title {
        margin-bottom: 28rpx;
        font-size: 32rpx;
        font-weight: 500;
        color: #222324;
        text-align: center;
    }
  uploading.value = true
  uni.showLoading({ title: '上传中...', mask: true })
  let failedCount = 0
  try {
    const uploadResults = await Promise.all(
      normalizedFiles.map(async file => {
        try {
          return { success: true, file: await uploadSelectedFile(file) }
        } catch {
          return { success: false, file }
        }
      })
    )
    const uploadedFiles = uploadResults.filter(item => item.success).map(item => item.file)
    failedCount = uploadResults.length - uploadedFiles.length
    attachmentList.value.push(...uploadedFiles)
  } finally {
    uni.hideLoading()
    uploading.value = false
  }
  uni.showToast({
    title: failedCount ? `${failedCount}个文件上传失败` : '上传成功',
    icon: failedCount ? 'none' : 'success',
  })
}
    .complete-input-wrapper {
        position: relative;
        box-sizing: border-box;
        height: 220rpx;
        padding-bottom: 38rpx;
        background: #f6f7f9;
        border: 1rpx solid #e5e7eb;
        border-radius: 6rpx;
    }
// 处理选择文件失败
function handleChooseFail(error) {
  if (error?.errMsg?.includes('cancel')) return
  uni.showToast({ title: '取消选择文件', icon: 'none' })
}
    .complete-input {
        box-sizing: border-box;
        width: 100%;
        height: 100%;
        padding: 20rpx;
        font-size: 28rpx;
        line-height: 1.5;
        color: #222324;
    }
// 选择附件
function handleChooseFile() {
  if (uploading.value) return
  const availableCount = MAX_FILE_COUNT - attachmentList.value.length
  if (availableCount <= 0) {
    uni.showToast({ title: `最多选择${MAX_FILE_COUNT}个附件`, icon: 'none' })
    return
  }
  const options = {
    count: availableCount,
    type: 'all',
    success: handleChooseSuccess,
    fail: handleChooseFail,
  }
    .complete-word-count {
        position: absolute;
        right: 16rpx;
        bottom: 10rpx;
        font-size: 22rpx;
        color: #999999;
    }
  // #ifdef H5
  uni.chooseFile(options)
  // #endif
    .complete-modal-actions {
        display: flex;
        gap: 24rpx;
        margin-top: 30rpx;
    }
  // #ifdef MP-WEIXIN
  uni.chooseMessageFile(options)
  // #endif
    .complete-action-btn {
        height: 76rpx;
        margin: 0;
        flex: 1;
        font-size: 28rpx;
        line-height: 76rpx;
        border-radius: 6rpx;
    }
  // #ifdef APP-PLUS
  uni.chooseImage({ ...options, sourceType: ['album', 'camera'] })
  // #endif
}
    .complete-action-btn::after {
        border: 0;
    }
// 移除附件
function handleRemoveFile(index) {
  attachmentList.value.splice(index, 1)
}
    .complete-cancel-btn {
        color: #666666;
        background: #f1f2f4;
    }
// 格式化文件大小
function formatFileSize(size) {
  if (size < 1024) return `${size} B`
  if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`
  return `${(size / 1024 / 1024).toFixed(1)} MB`
}
    .complete-confirm-btn {
        color: #ffffff;
        background: #1d6fe9;
    }
// 取消处置
function handleCancel() {
  uni.navigateBack()
}
    .complete-action-btn[disabled] {
        opacity: 0.65;
    }
// 提交处置信息
async function handleSubmit() {
  if (uploading.value) {
    uni.showToast({ title: '请等待附件上传完成', icon: 'none' })
    return
  }
  const disposeContent = formData.value.disposeContent.trim()
  if (!disposeContent) {
    uni.showToast({ title: '请输入处置描述', icon: 'none' })
    return
  }
  if (!eventId.value) {
    uni.showToast({ title: '工单信息缺失', icon: 'none' })
    return
  }
    /* 分享弹出层样式 */
    .share-modal-overlay {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: rgba(0, 0, 0, 0.6);
        display: flex;
        justify-content: flex-end;
        align-items: flex-end;
        z-index: 9999;
    }
  submitting.value = true
  try {
    await addDisposeRecord({
      eventId: eventId.value,
      recordType: 1,
      disposeContent: formData.value.disposeContent,
      targetEventStatus: eventStatus.value == 1 ? 3 : eventStatus.value,
      updateEventStatus: true,
      attachUrl: attachmentList.value.map(file => file.link),
      // operateTime: '2026-07-30 14:00:00',
      // eventStatus: formData.value.isHandling ? '1' : '3',
      // handleDesc,
      // attachmentList: attachmentList.value.map(file => ({
      //     attachName: file.name,
      //     filePath: file.path,
      // })),
    })
    uni.showToast({ title: '提交成功', icon: 'success' })
    setTimeout(() => {
      submitting.value = false
      uni.navigateBack()
      // uni.navigateTo({
      //     url: `/subPackages/workDetail/HandleWork/index?id=${eventId.value}`,
      // })
    }, 1200)
  } catch {
    submitting.value = false
    uni.showToast({ title: '提交失败,请重试', icon: 'none' })
  }
}
    .share-modal-content {
        background-color: #fff;
        border-radius: 20rpx 20rpx 0 0;
        padding: 30rpx;
        width: 100%;
        max-height: 80vh;
        overflow-y: auto;
    }
// 返回按钮点击事件
const onBackClick = () => {
  uni.navigateBack()
}
    .share-modal-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 30rpx;
    }
onLoad(options => {
  console.log(options, '9090')
  eventId.value = options?.id || ''
  eventStatus.value = options?.eventStatus || ''
})
</script>
    .share-modal-title {
        font-size: 36rpx;
        font-weight: bold;
        color: #222324;
        text-align: center;
        flex: 1;
    }
<style scoped lang="scss">
.handle-page {
  padding-top: 88rpx;
  :deep() {
    .u-navbar__content {
      background: url('https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_app_assets/images/user/bg.png')
      no-repeat !important;
      background-size: 100% !important;
      height: 88rpx !important;
    }
  }
    .share-modal-close {
        font-size: 60rpx;
        color: #999;
        line-height: 40rpx;
    }
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  height: calc(100vh);
  //padding-top: 88rpx;
  //background: #111827;
  //color: #f5f7fa;
    .share-modal-body {
        display: flex;
        flex-wrap: wrap;
        justify-content: space-around;
        padding-bottom: 30rpx;
    }
  //:deep(.u-navbar__content) {
  //    height: 88rpx !important;
  //    background: #151c29 !important;
  //}
}
    .share-modal-footer {
        display: flex;
        justify-content: space-around;
        padding-top: 30rpx;
        border-top: 1px solid #f0f0f0;
    }
.form-scroll {
    // #ifdef APP-PLUS
    padding-top: 60rpx;
    // #endif
  box-sizing: border-box;
  height: 0;
  flex: 1;
  padding: 0 24rpx 24rpx;
}
    .share-item {
        display: flex;
        flex-direction: column;
        align-items: center;
        margin: 20rpx;
        width: 120rpx;
    }
.form-section {
  box-sizing: border-box;
  margin-bottom: 20rpx;
  background: #ffffff;
  border: 1rpx solid rgba(255, 255, 255, 0.03);
}
    .share-icon {
        width: 90rpx;
        height: 90rpx;
        border-radius: 50%;
        margin-bottom: 15rpx;
        background-size: contain;
        background-repeat: no-repeat;
        background-position: center;
    }
.section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 82rpx;
  padding: 0 22rpx;
  border-bottom: 1rpx solid #eeeeee;
}
    .share-icon.wechat {
        background-image: url('@/static/images/work/wechat.svg');
    }
.section-title {
  font-size: 30rpx;
  font-weight: 400;
  color: #222324;
}
    .share-icon.moments {
        background-image: url('@/static/images/work/moments.svg');
    }
.description-wrapper {
  position: relative;
}
    .share-icon.link {
        background-image: url('@/static/images/work/link.svg');
    }
    .share-name {
        font-size: 24rpx;
        color: #666;
        margin-top: 10rpx;
    }
.word-count {
  position: absolute;
  right: 22rpx;
  bottom: 16rpx;
  font-size: 24rpx;
  color: #999;
}
.description-input {
  display: block;
  box-sizing: border-box;
  width: 100%;
  height: 350rpx;
  padding: 24rpx 22rpx;
  font-size: 30rpx;
  line-height: 1.6;
  color: #222324;
  background: transparent;
}
:deep(.description-placeholder) {
  color: #999;
}
.status-section {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 104rpx;
  padding: 0 20rpx 0 22rpx;
}
.status-switch {
  transform: scale(0.84);
  transform-origin: right center;
}
.upload-section {
  padding-bottom: 22rpx;
}
.upload-header {
  margin-bottom: 0;
}
.select-file-btn {
  width: 210rpx;
  height: 70rpx;
  margin: 0;
  font-size: 28rpx;
  line-height: 70rpx;
  //color: #ffffff;
  //background: #409eff;
  //border-radius: 4rpx;
}
.select-file-btn::after,
.action-btn::after {
  border: 0;
}
.custom-style {
  width: 276rpx;
  height: 76rpx;
  font-family: 'Source Han Sans CN';
  font-weight: 400;
  font-size: 28rpx;
}
.attachment-list {
  padding: 10rpx 22rpx 0;
}
.attachment-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 70rpx;
  border-bottom: 1rpx solid #eeeeee;
}
.file-info {
  display: flex;
  align-items: center;
  width: 0;
  flex: 1;
}
.file-name {
  overflow: hidden;
  font-size: 26rpx;
  color: #222324;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.file-size {
  margin-left: 14rpx;
  font-size: 22rpx;
  color: #999;
  white-space: nowrap;
}
.remove-file-btn {
  width: 56rpx;
  font-size: 42rpx;
  line-height: 56rpx;
  color: #999;
  text-align: right;
}
.action-footer {
  display: flex;
  align-items: center;
  gap: 28rpx;
  box-sizing: border-box;
  padding: 22rpx 28rpx calc(22rpx + env(safe-area-inset-bottom));
  //background: #111827;
}
.action-btn {
  height: 82rpx;
  margin: 0;
  flex: 1;
  font-size: 30rpx;
  line-height: 80rpx;
  color: #ffffff;
  border-radius: 6rpx;
}
.cancel-btn {
  color: #00b578;
  background: transparent;
  border: 2rpx solid #00b578;
}
.confirm-btn {
  background: #00b578;
  border: 2rpx solid #00b578;
}
.confirm-btn[disabled] {
  color: rgba(255, 255, 255, 0.72);
  background: #087b58;
}
</style>