|
@@ -0,0 +1,604 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <view class="page">
|
|
|
|
|
+ <scroll-view class="content" scroll-y>
|
|
|
|
|
+ <!-- 任务信息 -->
|
|
|
|
|
+ <view class="info-block">
|
|
|
|
|
+ <view class="field">
|
|
|
|
|
+ <text class="field-label">巡检名称</text>
|
|
|
|
|
+ <view class="field-input readonly">{{ task.taskName || '—' }}</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="field">
|
|
|
|
|
+ <text class="field-label">巡检位置</text>
|
|
|
|
|
+ <view
|
|
|
|
|
+ :class="['field-input', 'location', { readonly: readonly }]"
|
|
|
|
|
+ @tap="onPickLocation"
|
|
|
|
|
+ >
|
|
|
|
|
+ <text class="location-icon">📍</text>
|
|
|
|
|
+ <text class="location-text">{{ pickedLocation || task.location || (readonly ? '—' : '点击选择位置') }}</text>
|
|
|
|
|
+ <text v-if="!readonly" class="location-arrow">›</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 巡检项目列表 -->
|
|
|
|
|
+ <view class="section">
|
|
|
|
|
+ <view class="section-title">巡检项目列表</view>
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="(item, idx) in items"
|
|
|
|
|
+ :key="item.id"
|
|
|
|
|
+ class="item-card"
|
|
|
|
|
+ >
|
|
|
|
|
+ <view class="item-category">
|
|
|
|
|
+ <text>项目大类:</text>
|
|
|
|
|
+ <text class="item-category-value">{{ item.itemCategory || '-' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="item-row">
|
|
|
|
|
+ <text class="item-label">巡检内容</text>
|
|
|
|
|
+ <text class="item-text">{{ item.itemContent || '-' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="item-row">
|
|
|
|
|
+ <text class="item-label">巡检标准</text>
|
|
|
|
|
+ <text class="item-text">{{ item.itemStandard || '-' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="item-row">
|
|
|
|
|
+ <text class="item-label">检查结果</text>
|
|
|
|
|
+ <view class="item-result">
|
|
|
|
|
+ <button
|
|
|
|
|
+ :class="['result-btn', { active: item.result === 'NORMAL' }]"
|
|
|
|
|
+ :disabled="readonly"
|
|
|
|
|
+ @tap="onResultTap(item, 'NORMAL')"
|
|
|
|
|
+ >正常</button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ :class="['result-btn', { active: item.result === 'ABNORMAL' }]"
|
|
|
|
|
+ :disabled="readonly"
|
|
|
|
|
+ @tap="onResultTap(item, 'ABNORMAL')"
|
|
|
|
|
+ >异常</button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 异常原因(选择异常时显示) -->
|
|
|
|
|
+ <view v-if="item.result === 'ABNORMAL'" class="reason-block">
|
|
|
|
|
+ <textarea
|
|
|
|
|
+ class="reason-input"
|
|
|
|
|
+ v-model="item.reason"
|
|
|
|
|
+ :disabled="readonly"
|
|
|
|
|
+ placeholder="请填写异常原因(必填)"
|
|
|
|
|
+ maxlength="200"
|
|
|
|
|
+ />
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 拍照上传(needPhoto=true 的项) -->
|
|
|
|
|
+ <view v-if="item.needPhoto" class="photo-block">
|
|
|
|
|
+ <text class="item-label">现场照片</text>
|
|
|
|
|
+ <view class="photo-list">
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="(p, i) in (item.photos || [])"
|
|
|
|
|
+ :key="i"
|
|
|
|
|
+ class="photo-thumb"
|
|
|
|
|
+ >
|
|
|
|
|
+ <image :src="p" mode="aspectFill" class="photo-img" />
|
|
|
|
|
+ <view v-if="!readonly" class="photo-del" @tap="delPhoto(item, i)">×</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="!readonly" class="photo-add" @tap="choosePhoto(item)">
|
|
|
|
|
+ <text class="photo-add-icon">📷</text>
|
|
|
|
|
+ <text class="photo-add-text">添加照片</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </scroll-view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 底部提交按钮 -->
|
|
|
|
|
+ <view v-if="!readonly" class="footer safe-area-bottom">
|
|
|
|
|
+ <button class="btn btn-primary btn-block" @tap="onSubmit">提交打卡</button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 提交成功弹窗 -->
|
|
|
|
|
+ <view v-if="showSuccess" class="success-modal">
|
|
|
|
|
+ <view class="success-card">
|
|
|
|
|
+ <view class="success-icon">✓</view>
|
|
|
|
|
+ <text class="success-title">提交成功</text>
|
|
|
|
|
+ <text class="success-desc">巡检结果已成功保存</text>
|
|
|
|
|
+ <button class="btn btn-primary btn-block" @tap="onSuccessConfirm">返回巡检列表</button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { ref } from 'vue'
|
|
|
|
|
+import { onLoad } from '@dcloudio/uni-app'
|
|
|
|
|
+import type { QualityInspectionItem, QualityInspectionTaskDetail, QualityPunchSubmitPayload } from '@/types/qualityInspection'
|
|
|
|
|
+import {
|
|
|
|
|
+ getQualityTaskDetail,
|
|
|
|
|
+ submitQualityPunch,
|
|
|
|
|
+ uploadQualityPhoto
|
|
|
|
|
+} from '@/api/qualityInspection'
|
|
|
|
|
+import { request } from '@/utils/request'
|
|
|
|
|
+import { getToken } from '@/utils/auth'
|
|
|
|
|
+import { getCurrentLocation, ensureLocationAuth } from '@/utils/location'
|
|
|
|
|
+
|
|
|
|
|
+const taskId = ref<number>(0)
|
|
|
|
|
+const readonly = ref<boolean>(false)
|
|
|
|
|
+const task = ref<Partial<QualityInspectionTaskDetail>>({})
|
|
|
|
|
+const items = ref<QualityInspectionItem[]>([])
|
|
|
|
|
+const showSuccess = ref(false)
|
|
|
|
|
+// 用户在地图上选的打卡位置
|
|
|
|
|
+const pickedLocation = ref<string>('')
|
|
|
|
|
+// 用户选位置时取到的坐标(覆盖 GPS 自动获取)
|
|
|
|
|
+const pickedCoord = ref<{ lng: number; lat: number } | null>(null)
|
|
|
|
|
+
|
|
|
|
|
+onLoad((q) => {
|
|
|
|
|
+ taskId.value = Number(q?.taskId || 0)
|
|
|
|
|
+ readonly.value = q?.readonly === '1'
|
|
|
|
|
+ // 扫码页会带 lng/lat 过来,作为初值
|
|
|
|
|
+ if (q?.lng && q?.lat) {
|
|
|
|
|
+ pickedCoord.value = {
|
|
|
|
|
+ lng: Number(q.lng),
|
|
|
|
|
+ lat: Number(q.lat)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ loadDetail()
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+async function loadDetail() {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const data = await getQualityTaskDetail(taskId.value)
|
|
|
|
|
+ if (data) {
|
|
|
|
|
+ task.value = data
|
|
|
|
|
+ items.value = (data.items || []).map((it) => ({
|
|
|
|
|
+ ...it,
|
|
|
|
|
+ result: undefined,
|
|
|
|
|
+ reason: '',
|
|
|
|
|
+ photos: []
|
|
|
|
|
+ }))
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e: any) {
|
|
|
|
|
+ task.value = {} as any
|
|
|
|
|
+ items.value = []
|
|
|
|
|
+ uni.showToast({
|
|
|
|
|
+ title: e?.message || '任务详情加载失败',
|
|
|
|
|
+ icon: 'none',
|
|
|
|
|
+ duration: 2000
|
|
|
|
|
+ })
|
|
|
|
|
+ console.error('[品控巡检详情] 加载失败', e)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onResultTap(item: QualityInspectionItem, r: 'NORMAL' | 'ABNORMAL') {
|
|
|
|
|
+ if (readonly.value) return
|
|
|
|
|
+ if (item.result === r) {
|
|
|
|
|
+ item.result = undefined
|
|
|
|
|
+ item.reason = ''
|
|
|
|
|
+ } else {
|
|
|
|
|
+ item.result = r
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 拍照/选图 → 逐张上传 MinIO → 拿 URL 写入 item.photos
|
|
|
|
|
+ */
|
|
|
|
|
+async function choosePhoto(item: QualityInspectionItem) {
|
|
|
|
|
+ if (readonly.value) return
|
|
|
|
|
+ const pickRes = await new Promise<UniApp.ChooseImageSuccessCallbackResultFile>((resolve, reject) => {
|
|
|
|
|
+ uni.chooseImage({
|
|
|
|
|
+ count: 9,
|
|
|
|
|
+ success: (res) => resolve(res as any),
|
|
|
|
|
+ fail: (err) => reject(err)
|
|
|
|
|
+ })
|
|
|
|
|
+ }).catch((e) => {
|
|
|
|
|
+ if (e?.errMsg && !/cancel/i.test(e.errMsg)) {
|
|
|
|
|
+ uni.showToast({ title: '选图失败', icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ return null
|
|
|
|
|
+ })
|
|
|
|
|
+ if (!pickRes) return
|
|
|
|
|
+
|
|
|
|
|
+ const paths = pickRes.tempFilePaths || []
|
|
|
|
|
+ if (!paths.length) return
|
|
|
|
|
+
|
|
|
|
|
+ const uploaded: Array<{ url: string; ossId: string }> = []
|
|
|
|
|
+ const failed: string[] = []
|
|
|
|
|
+ uni.showLoading({ title: `上传中 0/${paths.length}…`, mask: true })
|
|
|
|
|
+ for (let i = 0; i < paths.length; i++) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const r = await uploadQualityPhoto(paths[i])
|
|
|
|
|
+ uploaded.push(r)
|
|
|
|
|
+ uni.showLoading({ title: `上传中 ${i + 1}/${paths.length}…`, mask: true })
|
|
|
|
|
+ } catch (e: any) {
|
|
|
|
|
+ failed.push(paths[i])
|
|
|
|
|
+ console.error('[品控打卡] 照片上传失败', e)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ uni.hideLoading()
|
|
|
|
|
+
|
|
|
|
|
+ if (failed.length) {
|
|
|
|
|
+ if (uploaded.length) {
|
|
|
|
|
+ rollbackUploaded(uploaded)
|
|
|
|
|
+ }
|
|
|
|
|
+ uni.showToast({
|
|
|
|
|
+ title: `${failed.length} 张上传失败,请重试`,
|
|
|
|
|
+ icon: 'none',
|
|
|
|
|
+ duration: 2500
|
|
|
|
|
+ })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!item.photos) item.photos = []
|
|
|
|
|
+ item.photos.push(...uploaded.map((u) => u.url))
|
|
|
|
|
+ items.value = [...items.value]
|
|
|
|
|
+ uni.showToast({ title: `已上传 ${uploaded.length} 张`, icon: 'success' })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 回滚已上传但未使用的对象 */
|
|
|
|
|
+function rollbackUploaded(list: Array<{ url: string; ossId: string }>) {
|
|
|
|
|
+ for (const u of list) {
|
|
|
|
|
+ if (!u.ossId) continue
|
|
|
|
|
+ request({
|
|
|
|
|
+ url: `/resource/oss/${u.ossId}`,
|
|
|
|
|
+ method: 'DELETE',
|
|
|
|
|
+ skipAuth: !getToken()
|
|
|
|
|
+ }).catch((e) => console.warn('[品控打卡] 回滚 oss 失败', u.ossId, e))
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function delPhoto(item: QualityInspectionItem, idx: number) {
|
|
|
|
|
+ if (readonly.value) return
|
|
|
|
|
+ item.photos?.splice(idx, 1)
|
|
|
|
|
+ items.value = [...items.value]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onPickLocation() {
|
|
|
|
|
+ if (readonly.value) return
|
|
|
|
|
+ // #ifdef APP-PLUS || MP-WEIXIN || H5
|
|
|
|
|
+ uni.chooseLocation({
|
|
|
|
|
+ success: (res: any) => {
|
|
|
|
|
+ const name = (res.name || res.address || '').toString().trim()
|
|
|
|
|
+ if (name) pickedLocation.value = name
|
|
|
|
|
+ if (typeof res.latitude === 'number' && typeof res.longitude === 'number') {
|
|
|
|
|
+ pickedCoord.value = { lng: res.longitude, lat: res.latitude }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ fail: (err: any) => {
|
|
|
|
|
+ if (err && err.errMsg && !/cancel|deny/i.test(err.errMsg)) {
|
|
|
|
|
+ uni.showToast({ title: '选点失败,请重试', icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ // #endif
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function validate(): { ok: boolean; msg: string } {
|
|
|
|
|
+ for (let i = 0; i < items.value.length; i++) {
|
|
|
|
|
+ const it = items.value[i]
|
|
|
|
|
+ if (!it.result) {
|
|
|
|
|
+ return { ok: false, msg: `第 ${i + 1} 项巡检项目未确认` }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (it.result === 'ABNORMAL' && !it.reason?.trim()) {
|
|
|
|
|
+ return { ok: false, msg: `第 ${i + 1} 项已选异常,请填写异常原因` }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (it.needPhoto && (!it.photos || it.photos.length === 0)) {
|
|
|
|
|
+ return { ok: false, msg: `第 ${i + 1} 项需要拍照,请上传现场照片` }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return { ok: true, msg: '' }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function onSubmit() {
|
|
|
|
|
+ const v = validate()
|
|
|
|
|
+ if (!v.ok) {
|
|
|
|
|
+ uni.showToast({ title: v.msg, icon: 'none' })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const finalLocation = pickedLocation.value || task.value.location || ''
|
|
|
|
|
+ if (!finalLocation) {
|
|
|
|
|
+ uni.showToast({ title: '请先选择巡检位置', icon: 'none' })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ uni.showLoading({ title: '提交中…' })
|
|
|
|
|
+ try {
|
|
|
|
|
+ let lng = 0
|
|
|
|
|
+ let lat = 0
|
|
|
|
|
+ if (pickedCoord.value) {
|
|
|
|
|
+ lng = pickedCoord.value.lng
|
|
|
|
|
+ lat = pickedCoord.value.lat
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const auth = await ensureLocationAuth()
|
|
|
|
|
+ if (auth) {
|
|
|
|
|
+ const loc = await getCurrentLocation()
|
|
|
|
|
+ lng = loc.longitude
|
|
|
|
|
+ lat = loc.latitude
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const allPhotoUrls = collectAllPhotos()
|
|
|
|
|
+
|
|
|
|
|
+ const payload: QualityPunchSubmitPayload = {
|
|
|
|
|
+ taskId: taskId.value,
|
|
|
|
|
+ taskName: task.value.taskName,
|
|
|
|
|
+ storeId: task.value.storeId,
|
|
|
|
|
+ storeName: task.value.storeName,
|
|
|
|
|
+ punchTime: new Date().toISOString(),
|
|
|
|
|
+ punchLocation: finalLocation,
|
|
|
|
|
+ lng,
|
|
|
|
|
+ lat,
|
|
|
|
|
+ punchType: task.value.punchType || 'NORMAL',
|
|
|
|
|
+ photos: allPhotoUrls || undefined,
|
|
|
|
|
+ items: items.value.map((it) => ({
|
|
|
|
|
+ itemId: it.id,
|
|
|
|
|
+ itemName: it.itemContent,
|
|
|
|
|
+ itemType: it.itemCategory,
|
|
|
|
|
+ standardValue: it.itemStandard,
|
|
|
|
|
+ result: it.result === 'ABNORMAL' ? ('1' as const) : ('0' as const),
|
|
|
|
|
+ reason: it.result === 'ABNORMAL' ? it.reason : undefined,
|
|
|
|
|
+ photos: (it.photos && it.photos.length) ? it.photos.join(',') : undefined
|
|
|
|
|
+ }))
|
|
|
|
|
+ }
|
|
|
|
|
+ await submitQualityPunch(payload)
|
|
|
|
|
+ uni.hideLoading()
|
|
|
|
|
+ showSuccess.value = true
|
|
|
|
|
+ } catch (e: any) {
|
|
|
|
|
+ uni.hideLoading()
|
|
|
|
|
+ uni.showToast({
|
|
|
|
|
+ title: e?.message || '提交失败,请重试',
|
|
|
|
|
+ icon: 'none',
|
|
|
|
|
+ duration: 2500
|
|
|
|
|
+ })
|
|
|
|
|
+ console.error('[品控打卡] 提交失败', e)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function collectAllPhotos(): string {
|
|
|
|
|
+ const set = new Set<string>()
|
|
|
|
|
+ for (const it of items.value) {
|
|
|
|
|
+ if (it.photos && it.photos.length) {
|
|
|
|
|
+ for (const p of it.photos) set.add(p)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return Array.from(set).join(',')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onSuccessConfirm() {
|
|
|
|
|
+ uni.navigateBack({ delta: 1 })
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+@import "@/static/styles/variables.scss";
|
|
|
|
|
+
|
|
|
|
|
+.page {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ height: 100vh;
|
|
|
|
|
+ background: $bg-page;
|
|
|
|
|
+}
|
|
|
|
|
+.content {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ padding: 24rpx;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 任务信息 */
|
|
|
|
|
+.info-block {
|
|
|
|
|
+ background: $bg-card;
|
|
|
|
|
+ border-radius: $radius-lg;
|
|
|
|
|
+ padding: 24rpx;
|
|
|
|
|
+ margin-bottom: 24rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.field {
|
|
|
|
|
+ margin-bottom: 24rpx;
|
|
|
|
|
+ &:last-child { margin-bottom: 0; }
|
|
|
|
|
+}
|
|
|
|
|
+.field-label {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ font-size: $font-sm;
|
|
|
|
|
+ color: $text-secondary;
|
|
|
|
|
+ margin-bottom: 12rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.field-input {
|
|
|
|
|
+ background: $bg-page;
|
|
|
|
|
+ border-radius: $radius-md;
|
|
|
|
|
+ padding: 24rpx;
|
|
|
|
|
+ font-size: $font-base;
|
|
|
|
|
+ color: $text-primary;
|
|
|
|
|
+ min-height: 80rpx;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ &.readonly { background: $bg-page; }
|
|
|
|
|
+ &.location {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ .location-icon {
|
|
|
|
|
+ margin-right: 8rpx;
|
|
|
|
|
+ color: $primary;
|
|
|
|
|
+ }
|
|
|
|
|
+ .location-text {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ }
|
|
|
|
|
+ .location-arrow {
|
|
|
|
|
+ margin-left: 8rpx;
|
|
|
|
|
+ color: $text-secondary;
|
|
|
|
|
+ font-size: 36rpx;
|
|
|
|
|
+ line-height: 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 巡检项目 */
|
|
|
|
|
+.section {
|
|
|
|
|
+ background: $bg-card;
|
|
|
|
|
+ border-radius: $radius-lg;
|
|
|
|
|
+ padding: 24rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.section-title {
|
|
|
|
|
+ font-size: $font-md;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: $text-primary;
|
|
|
|
|
+ margin-bottom: 24rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.item-card {
|
|
|
|
|
+ padding: 24rpx;
|
|
|
|
|
+ background: $bg-page;
|
|
|
|
|
+ border-radius: $radius-md;
|
|
|
|
|
+ margin-bottom: 24rpx;
|
|
|
|
|
+ &:last-child { margin-bottom: 0; }
|
|
|
|
|
+}
|
|
|
|
|
+.item-category {
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ background: $primary-bg;
|
|
|
|
|
+ color: $primary;
|
|
|
|
|
+ padding: 8rpx 16rpx;
|
|
|
|
|
+ border-radius: $radius-sm;
|
|
|
|
|
+ font-size: $font-xs;
|
|
|
|
|
+ margin-bottom: 16rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.item-row {
|
|
|
|
|
+ margin-bottom: 16rpx;
|
|
|
|
|
+ &:last-child { margin-bottom: 0; }
|
|
|
|
|
+}
|
|
|
|
|
+.item-label {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ font-size: $font-sm;
|
|
|
|
|
+ color: $text-secondary;
|
|
|
|
|
+ margin-bottom: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.item-text {
|
|
|
|
|
+ font-size: $font-base;
|
|
|
|
|
+ color: $text-primary;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+}
|
|
|
|
|
+.item-result {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 24rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.result-btn {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ height: 72rpx;
|
|
|
|
|
+ border: 2rpx solid $border-color;
|
|
|
|
|
+ background: $bg-card;
|
|
|
|
|
+ color: $text-regular;
|
|
|
|
|
+ border-radius: $radius-md;
|
|
|
|
|
+ font-size: $font-base;
|
|
|
|
|
+ &::after { border: none; }
|
|
|
|
|
+ &.active {
|
|
|
|
|
+ background: $primary;
|
|
|
|
|
+ color: #FFFFFF;
|
|
|
|
|
+ border-color: $primary;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+.reason-block { margin-top: 16rpx; }
|
|
|
|
|
+.reason-input {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ min-height: 120rpx;
|
|
|
|
|
+ padding: 16rpx;
|
|
|
|
|
+ background: $bg-card;
|
|
|
|
|
+ border-radius: $radius-md;
|
|
|
|
|
+ font-size: $font-base;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+}
|
|
|
|
|
+.photo-block { margin-top: 16rpx; }
|
|
|
|
|
+.photo-list {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 16rpx;
|
|
|
|
|
+ margin-top: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.photo-thumb {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ width: 160rpx;
|
|
|
|
|
+ height: 160rpx;
|
|
|
|
|
+ border-radius: $radius-md;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+.photo-img { width: 100%; height: 100%; }
|
|
|
|
|
+.photo-del {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: -8rpx;
|
|
|
|
|
+ right: -8rpx;
|
|
|
|
|
+ width: 36rpx;
|
|
|
|
|
+ height: 36rpx;
|
|
|
|
|
+ line-height: 36rpx;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ background: rgba(0, 0, 0, 0.6);
|
|
|
|
|
+ color: #FFFFFF;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ font-size: 24rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.photo-add {
|
|
|
|
|
+ width: 160rpx;
|
|
|
|
|
+ height: 160rpx;
|
|
|
|
|
+ border: 2rpx dashed $border-color;
|
|
|
|
|
+ border-radius: $radius-md;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ color: $text-secondary;
|
|
|
|
|
+}
|
|
|
|
|
+.photo-add-icon {
|
|
|
|
|
+ font-size: 48rpx;
|
|
|
|
|
+ margin-bottom: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.photo-add-text {
|
|
|
|
|
+ font-size: $font-xs;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 底部 */
|
|
|
|
|
+.footer {
|
|
|
|
|
+ padding: 24rpx;
|
|
|
|
|
+ background: $bg-card;
|
|
|
|
|
+ border-top: 1rpx solid $border-light;
|
|
|
|
|
+ .btn { height: 88rpx; font-size: $font-md; }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 成功弹窗 */
|
|
|
|
|
+.success-modal {
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ inset: 0;
|
|
|
|
|
+ background: rgba(0, 0, 0, 0.5);
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ z-index: $z-modal;
|
|
|
|
|
+}
|
|
|
|
|
+.success-card {
|
|
|
|
|
+ width: 560rpx;
|
|
|
|
|
+ background: $bg-card;
|
|
|
|
|
+ border-radius: $radius-lg;
|
|
|
|
|
+ padding: 48rpx 32rpx 32rpx;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+.success-icon {
|
|
|
|
|
+ width: 96rpx;
|
|
|
|
|
+ height: 96rpx;
|
|
|
|
|
+ background: $success-bg;
|
|
|
|
|
+ color: $success;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ font-size: 56rpx;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ margin-bottom: 24rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.success-title {
|
|
|
|
|
+ font-size: 36rpx;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: $text-primary;
|
|
|
|
|
+ margin-bottom: 16rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.success-desc {
|
|
|
|
|
+ font-size: $font-sm;
|
|
|
|
|
+ color: $text-secondary;
|
|
|
|
|
+ margin-bottom: 32rpx;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|