examPointVenueImage.vue 5.11 KB
<template>
  <view class="venue-page">
    <view class="content-card">
      <view class="title">上传场馆照片</view>
      <view class="desc">请上传 1~3 张清晰的场馆照片,照片将作为考点申请材料提交审核。</view>

      <view class="photo-grid">
        <view
          v-for="(item, index) in photoList"
          :key="item.url"
          class="photo-item"
        >
          <image class="photo" :src="item.previewUrl" mode="aspectFill" @click="previewPhoto(index)" />
          <view class="delete-btn" @click.stop="deletePhoto(index)">×</view>
        </view>
        <view v-if="photoList.length < 3" class="upload-card" @click="choosePhoto">
          <view class="plus">+</view>
          <view class="upload-text">添加照片</view>
        </view>
      </view>

      <view class="tip">最多上传 3 张,支持从相册选择或拍照上传。</view>
    </view>

    <view class="footer">
      <button class="back-btn" @click="goBack">上一步</button>
      <button class="next-btn" @click="goNext">下一步</button>
    </view>
  </view>
</template>

<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { fillImgUrl } from '@/common/utils.js'
import config from '@/config.js'

const photoList = ref([])
const memId = ref('')

onLoad((option) => {
  memId.value = option.memId || ''
})

function choosePhoto() {
  const count = 3 - photoList.value.length
  if (count <= 0) return
  uni.chooseImage({
    count,
    sizeType: ['compressed'],
    sourceType: ['album', 'camera'],
    success: async (res) => {
      const paths = res.tempFilePaths || []
      for (const path of paths) {
        if (photoList.value.length >= 3) break
        await uploadPhoto(path)
      }
    }
  })
}

async function uploadPhoto(path) {
  if (!path) return
  try {
    uni.showLoading({ title: '上传中', mask: true })
    const res = await uploadApplyImage(path)
    const url = getUploadUrl(res)
    if (!url) {
      uni.showToast({ title: '上传返回数据异常', icon: 'none' })
      return
    }
    photoList.value.push({
      url,
      previewUrl: fillImgUrl(url)
    })
  } catch (err) {
    uni.showToast({ title: '上传失败,请重新上传', icon: 'none' })
  } finally {
    uni.hideLoading()
  }
}

function uploadApplyImage(path) {
  return new Promise((resolve, reject) => {
    uni.uploadFile({
      url: config.baseUrl_api + '/fileServer/uploadImg',
      filePath: path,
      name: 'image',
      header: {
        'Authorization': uni.getStorageSync('token')
      },
      success: (res) => {
        try {
          resolve(JSON.parse(res.data || '{}'))
        } catch (e) {
          reject(e)
        }
      },
      fail: reject
    })
  })
}

function getUploadUrl(res) {
  const data = res?.data
  if (typeof data === 'string') return data
  return data?.ms || data?.url || data?.fang || res?.msg || ''
}

function deletePhoto(index) {
  photoList.value.splice(index, 1)
}

function previewPhoto(index) {
  uni.previewImage({
    current: index,
    urls: photoList.value.map(item => item.previewUrl)
  })
}

function goNext() {
  if (photoList.value.length === 0) {
    uni.showToast({ title: '请上传1~3张场馆照片', icon: 'none' })
    return
  }
  const photos = encodeURIComponent(JSON.stringify(photoList.value.map(item => item.url)))
  uni.navigateTo({
    url: `/myCenter/examPointApply?photos=${photos}&memId=${memId.value || ''}`
  })
}

function goBack() {
  uni.navigateBack({ delta: 1 })
}
</script>

<style lang="scss" scoped>
.venue-page {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: #f5f5f5;
}

.content-card {
  flex: 1;
  margin: 30rpx;
  padding: 36rpx;
  background: #fff;
  border-radius: 18rpx;
}

.title {
  font-size: 34rpx;
  font-weight: 600;
  color: #222;
}

.desc {
  margin-top: 18rpx;
  color: #777;
  font-size: 26rpx;
  line-height: 1.6;
}

.photo-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 22rpx;
  margin-top: 36rpx;
}

.photo-item,
.upload-card {
  position: relative;
  width: 196rpx;
  height: 196rpx;
  border-radius: 16rpx;
  overflow: hidden;
}

.photo {
  width: 100%;
  height: 100%;
}

.delete-btn {
  position: absolute;
  top: 8rpx;
  right: 8rpx;
  width: 42rpx;
  height: 42rpx;
  line-height: 38rpx;
  text-align: center;
  border-radius: 50%;
  color: #fff;
  background: #C4121B;
  font-size: 34rpx;
}

.upload-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  border: 2rpx dashed #d7d7d7;
  background: #fafafa;
}

.plus {
  color: #C4121B;
  font-size: 58rpx;
  line-height: 1;
}

.upload-text {
  margin-top: 12rpx;
  color: #777;
  font-size: 24rpx;
}

.tip {
  margin-top: 26rpx;
  color: #999;
  font-size: 24rpx;
}

.footer {
  padding: 30rpx;
  display: flex;
  justify-content: space-between;
  background: #fff;
  box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
}

.back-btn,
.next-btn {
  width: 48%;
  height: 88rpx;
  line-height: 88rpx;
  border-radius: 44rpx;
  font-size: 32rpx;
}

.back-btn {
  background: #fff;
  color: #C4121B;
  border: 1rpx solid #C4121B;
}

.next-btn {
  background: #C4121B;
  color: #fff;
  border: none;
}
</style>