From e56792f78e4df0df2f12552d1a61dd8ca1db5c67 Mon Sep 17 00:00:00 2001
From: MrShi <1878285526@qq.com>
Date: 星期一, 27 四月 2026 22:26:28 +0800
Subject: [PATCH] app

---
 app/pages/order/order.vue |  733 +++++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 570 insertions(+), 163 deletions(-)

diff --git a/app/pages/order/order.vue b/app/pages/order/order.vue
index d81b4d4..faa455c 100644
--- a/app/pages/order/order.vue
+++ b/app/pages/order/order.vue
@@ -15,56 +15,132 @@
 
 		<scroll-view class="order-page__body" scroll-y :style="bodyStyle">
 			<view class="order-page__list">
-				<view v-for="item in currentOrders" :key="item.id" class="order-card" @click="goToOrderDetail(item)">
+				<view v-for="(item, index) in orders" :key="item.id" class="order-card" @click="goToOrderDetail(item, index)">
 					<view class="order-card__head">
 					<view class="order-card__head-left">
-						<image class="order-card__badge-icon" :src="getBadgeIcon(item.badge)" mode="widthFix"></image>
-						<text class="order-card__time-text">涓嬪崟鏃堕棿: {{ item.orderTime }}</text>
+						<image class="order-card__badge-icon" :src="getBadgeIcon(item)" mode="widthFix"></image>
+						<text class="order-card__time-text">涓嬪崟鏃堕棿锛歿{ item.createTime }}</text>
 					</view>
-						<text class="order-card__status" :class="{ 'order-card__status--highlight': item.actions && item.actions.length }">{{ item.statusText }}</text>
+						<text class="order-card__status" :class="{ 'order-card__status--highlight': item.status === 3 || item.status === 4 }">{{ getStatusText(item.status) }}</text>
 					</view>
 
 					<view class="order-card__route-item">
 						<view class="order-card__point order-card__point--pickup">鍙�</view>
 						<view class="order-card__route-texts">
-							<text class="order-card__route-title">{{ item.pickupName }}</text>
-							<text class="order-card__route-desc">{{ item.pickupAddress }}</text>
+							<text class="order-card__route-title">{{ item.takeName }}</text>
+							<text class="order-card__route-desc">{{ item.takeAddress }}</text>
 						</view>
 					</view>
 
 					<view class="order-card__route-item order-card__route-item--delivery">
 						<view class="order-card__point order-card__point--delivery">閫�</view>
 						<view class="order-card__route-texts">
-							<text class="order-card__route-title">{{ item.deliveryName }}</text>
-							<text class="order-card__route-desc">{{ item.deliveryAddress }}</text>
+							<text class="order-card__route-title">{{ item.depositShopName }}</text>
+							<text class="order-card__route-desc">{{ item.depositShopAddress }}</text>
 						</view>
 					</view>
 
 					<view class="order-card__footer">
 						<view class="order-card__arrival">
 							<image class="order-card__clock" src="/static/image/ic_clock@2x.png" mode="aspectFit"></image>
-							<text class="order-card__arrival-text">{{ item.arriveLabel || '閫佽揪鏃堕棿锛�' }}{{ item.arriveTime }}</text>
+							<text class="order-card__arrival-text">鍓╀綑{{ item.remainMinutes }}鍒嗛挓</text>
 						</view>
 						<view class="order-card__price-wrap">
-							<text v-if="item.priceTag" class="order-card__price-tag">{{ item.priceTag }}</text>
-							<text class="order-card__price">{{ item.price }}</text>
+							<text class="order-card__price">楼{{ (item.driverFee / 100).toFixed(2) }}</text>
 						</view>
 					</view>
 
-					<view v-if="item.actions && item.actions.length" class="order-card__actions">
-						<button
-							v-for="action in item.actions"
-							:key="action.text"
-							class="order-card__action-btn"
-							:class="['order-card__action-btn--' + action.type, { 'order-card__action-btn--primary-fill': action.fill }]"
-							hover-class="order-card__action-btn--hover"
-						>
-							{{ action.text }}
-						</button>
+					<view v-if="getActions(item).length" class="order-card__actions">
+						<view></view>
+						<view style="display: flex;flex-wrap: wrap;gap: 20rpx;">
+							<button
+								v-for="action in getActions(item)"
+								:key="action.text"
+								class="order-card__action-btn"
+								:class="['order-card__action-btn--' + action.type, { 'order-card__action-btn--primary-fill': action.fill }]"
+								hover-class="order-card__action-btn--hover"
+								@click.stop="handleAction(item, action)"
+							>
+								{{ action.text }}
+							</button>
+						</view>
+						
 					</view>
+				</view>
+
+				<view v-if="orders.length === 0 && !loading" class="order-page__empty">
+					<image class="order-page__empty-icon" src="/static/image/default_nodata@2x.png" mode="aspectFit"></image>
+				</view>
+
+				<view v-if="loading" class="order-page__loading">
+					<text>鍔犺浇涓�...</text>
 				</view>
 			</view>
 		</scroll-view>
+
+		<u-modal
+			:show="showCancelModal"
+			showCancelButton
+			@cancel="showCancelModal = false"
+			cancelColor="#666666"
+			confirmColor="#0055FF"
+			title="鍙栨秷璁㈠崟纭"
+			@confirm="confirmCancelOrder">
+			<view style="text-align: center;color: #333333;font-size: 28rpx;font-weight: 400;">
+				鎮ㄤ粖鏃ヨ繕鍙彇娑� {{ cancelRemain }} 娆¤鍗曪紝娆℃暟鐢ㄥ敖鍚庝粖鏃ュ皢鏃犳硶鎺ュ崟锛屾槸鍚︾‘璁ゅ彇娑堬紵
+			</view>
+		</u-modal>
+
+		<u-popup :show="showQRPopup" round="20" mode="bottom">
+			<view class="qrcode">
+				<view class="qrcode-title">
+					<image src="/static/image/ic_close@2x.png" mode="widthFix" style="opacity: 0;"></image>
+					<text>{{ selectedOrder && selectedOrder.status === 4 ? '瀛樹欢鐮�' : '鍙栬揣鐮�' }}</text>
+					<image src="/static/image/ic_close@2x.png" mode="widthFix" @click="showQRPopup = false"></image>
+				</view>
+				<view class="qrcode-image">
+					<image v-if="selectedOrder && selectedOrder.driverVerifyCode" :src="'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=' + selectedOrder.driverVerifyCode" mode="widthFix"></image>
+				</view>
+				<view class="qrcode-btn" hover-class="qrcode-btn--hover" @click="showQRPopup = false">鍏抽棴</view>
+			</view>
+		</u-popup>
+
+		<u-popup :show="showPhotoPopup" round="20" mode="bottom">
+			<view class="photo-popup">
+				<view class="photo-popup__header">
+					<view class="photo-popup__placeholder"></view>
+					<text class="photo-popup__title">{{ photoPopupTitle }}</text>
+					<image class="photo-popup__close" src="/static/image/ic_close2@2x.png" mode="aspectFit" @click="closePhotoPopup"></image>
+				</view>
+
+				<view class="photo-popup__section">
+					<view class="photo-popup__label-row">
+						<text class="photo-popup__label">{{ photoPopupLabel }}</text>
+						<text class="photo-popup__required">*</text>
+						<text class="photo-popup__hint">鏈�澶�3寮犵収鐗�</text>
+					</view>
+
+					<view class="photo-popup__photos">
+						<view v-for="(photo, index) in uploadedPhotos" :key="index" class="photo-popup__preview-card">
+							<image class="photo-popup__preview-image" :src="photo" mode="aspectFill"></image>
+							<view class="photo-popup__preview-mask" @click="deletePhoto(index)">
+								<text class="photo-popup__preview-delete">鍒犻櫎</text>
+							</view>
+						</view>
+						<view v-if="uploadedPhotos.length < 3" class="photo-popup__upload-btn" @click="choosePhoto">
+							<image src="/static/image/btn_upload2@2x.png" mode="aspectFit"></image>
+						</view>
+					</view>
+				</view>
+
+				<view class="photo-popup__section photo-popup__section--remark">
+					<text class="photo-popup__remark-title">澶囨敞淇℃伅</text>
+					<textarea v-model="photoRemark" class="photo-popup__textarea" maxlength="200" placeholder="璇疯緭鍏�" placeholder-style="color: #c7cbd3;" />
+				</view>
+
+				<button class="photo-popup__submit" hover-class="photo-popup__submit--hover" @click="submitPhotoPopup">{{ photoPopupSubmitText }}</button>
+			</view>
+		</u-popup>
 	</view>
 </template>
 
@@ -74,140 +150,44 @@
 			return {
 				statusBarHeight: 0,
 				navHeight: 0,
-				activeTab: 'all',
+				activeTab: null,
 				tabs: [
-					{ label: '鍏ㄩ儴', value: 'all' },
-					{ label: '寰呭彇璐�', value: 'pickup' },
-					{ label: '閰嶉�佷腑', value: 'delivering' },
-					{ label: '宸插畬鎴�', value: 'finished' }
+					{ label: '鍏ㄩ儴', value: null },
+					{ label: '寰呭彇璐�', value: 3 },
+					{ label: '閰嶉�佷腑', value: 4 },
+					{ label: '宸插畬鎴�', value: 7 }
 				],
-				orders: [
-					{
-						id: 1,
-						type: 'pickup',
-						badge: '鏍囬�熻揪',
-						badgeType: 'blue',
-						orderTime: '2026-04-12 12:09',
-						statusText: '寰呭彇璐�',
-						pickupName: '涓搧蹇繍鍗楃珯鏃楄埌搴�',
-						pickupAddress: '鑾茶姳璺�200鍙疯幉鑺变骇涓氬洯F鏍�401',
-						deliveryName: '浣宠嫅宸撮粠閮藉競3鏈�10鏍�301瀹�',
-						deliveryAddress: '娲炲涵婀栬矾涓庢箹鍖楄矾浜ゅ弶鍙hタ150绫�',
-						arriveLabel: '',
-						arriveTime: '45鍒嗛挓鍐呴�佽揪',
-						priceTag: '',
-						price: '楼20.5',
-						actions: [
-							{ text: '鍙栨秷璁㈠崟', type: 'light', fill: false },
-							{ text: '鍙栬揣鐮�', type: 'primary', fill: false },
-							{ text: '鎷嶇収鍙栬揣', type: 'primary', fill: true }
-						]
-					},
-					{
-						id: 4,
-						type: 'pickup',
-						badge: '鏋侀�熻揪',
-						badgeType: 'red',
-						orderTime: '2026-04-12 12:33',
-						statusText: '寰呭彇璐�',
-						pickupName: '涓搧蹇繍鍗楃珯鏃楄埌搴�',
-						pickupAddress: '鑾茶姳璺�200鍙疯幉鑺变骇涓氬洯F鏍�401',
-						deliveryName: '浣宠嫅宸撮粠閮藉競3鏈�10鏍�301瀹�',
-						deliveryAddress: '娲炲涵婀栬矾涓庢箹鍖楄矾浜ゅ弶鍙hタ150绫�',
-						arriveLabel: '',
-						arriveTime: '50鍒嗛挓鍐呴�佽揪',
-						priceTag: '',
-						price: '楼20.5',
-						actions: [
-							{ text: '鍙栨秷璁㈠崟', type: 'light', fill: false },
-							{ text: '鍙栬揣鐮�', type: 'primary', fill: false },
-							{ text: '鎷嶇収鍙栬揣', type: 'primary', fill: true }
-						]
-					},
-					{
-						id: 2,
-						type: 'delivering',
-						badge: '鏋侀�熻揪',
-						badgeType: 'red',
-						orderTime: '2026-04-12 12:33',
-						statusText: '閰嶉�佷腑',
-						pickupName: '涓搧蹇繍鍗楃珯鏃楄埌搴�',
-						pickupAddress: '鑾茶姳璺�200鍙疯幉鑺变骇涓氬洯F鏍�401',
-						deliveryName: '浣宠嫅宸撮粠閮藉競3鏈�10鏍�301瀹�',
-						deliveryAddress: '娲炲涵婀栬矾涓庢箹鍖楄矾浜ゅ弶鍙hタ150绫�',
-						arriveLabel: '閫佽揪鏃堕棿锛�',
-						arriveTime: '04-12 12:58',
-						priceTag: '',
-						price: '楼20.5'
-					},
-					{
-						id: 5,
-						type: 'rated',
-						badge: '鏋侀�熻揪',
-						badgeType: 'red',
-						orderTime: '2026-04-12 13:08',
-						statusText: '宸茶瘎浠�',
-						pickupName: '涓搧蹇繍鍗楃珯鏃楄埌搴�',
-						pickupAddress: '鑾茶姳璺�200鍙疯幉鑺变骇涓氬洯F鏍�401',
-						deliveryName: '浣宠嫅宸撮粠閮藉競3鏈�10鏍�301瀹�',
-						deliveryAddress: '娲炲涵婀栬矾涓庢箹鍖楄矾浜ゅ弶鍙hタ150绫�',
-						arriveLabel: '閫佽揪鏃堕棿锛�',
-						arriveTime: '04-12 13:36',
-						priceTag: '',
-						price: '楼18.8'
-					},
-					{
-						id: 6,
-						type: 'cancelled',
-						badge: '鏍囬�熻揪',
-						badgeType: 'blue',
-						orderTime: '2026-04-12 13:18',
-						statusText: '宸插彇娑�',
-						pickupName: '涓搧蹇繍鍗楃珯鏃楄埌搴�',
-						pickupAddress: '鑾茶姳璺�200鍙疯幉鑺变骇涓氬洯F鏍�401',
-						deliveryName: '浣宠嫅宸撮粠閮藉競3鏈�10鏍�301瀹�',
-						deliveryAddress: '娲炲涵婀栬矾涓庢箹鍖楄矾浜ゅ弶鍙hタ150绫�',
-						arriveLabel: '閫佽揪鏃堕棿锛�',
-						arriveTime: '04-12 13:52',
-						priceTag: '',
-						price: '楼16.5'
-					},
-					{
-						id: 3,
-						type: 'finished',
-						badge: '鏋侀�熻揪',
-						badgeType: 'red',
-						orderTime: '2026-04-12 12:33',
-						statusText: '宸插畬鎴�',
-						pickupName: '涓搧蹇繍鍗楃珯鏃楄埌搴�',
-						pickupAddress: '鑾茶姳璺�200鍙疯幉鑺变骇涓氬洯F鏍�401',
-						deliveryName: '浣宠嫅宸撮粠閮藉競3鏈�10鏍�301瀹�',
-						deliveryAddress: '娲炲涵婀栬矾涓庢箹鍖楄矾浜ゅ弶鍙hタ150绫�',
-						arriveLabel: '閫佽揪鏃堕棿锛�',
-						arriveTime: '04-12 12:58',
-						priceTag: '宸茬粨绠�',
-						price: '楼20.5'
-					}
-				]
+				orders: [],
+				page: 1,
+				hasMore: true,
+				loading: false,
+				showCancelModal: false,
+				showQRPopup: false,
+				showPhotoPopup: false,
+				selectedOrder: null,
+				cancelRemain: 0,
+				photoPopupMode: '',
+				photoRemark: '',
+				uploadedPhotos: [],
+				activeOrderCount: null
 			}
 		},
 		computed: {
 			displayTabs() {
-				const countMap = {
-					pickup: this.orders.filter((item) => item.type === 'pickup').length,
-					delivering: this.orders.filter((item) => item.type === 'delivering').length,
-					finished: this.orders.filter((item) => item.type === 'finished').length
-				}
-
 				return this.tabs.map((tab) => {
-					if (!countMap[tab.value]) {
-						return tab
+					let count = null
+					if (tab.value === 3) {
+						count = this.activeOrderCount?.grabbedCount
+					} else if (tab.value === 4) {
+						count = this.activeOrderCount?.deliveringCount
 					}
-
-					return {
-						...tab,
-						label: `${tab.label} ${countMap[tab.value]}`
+					if (count) {
+						return {
+							...tab,
+							label: `${tab.label} ${count}`
+						}
 					}
+					return tab
 				})
 			},
 
@@ -217,31 +197,237 @@
 					height: `calc(100vh - ${this.navHeight + uni.upx2px(88)}px)`
 				}
 			},
-			currentOrders() {
-				if (this.activeTab === 'all') {
-					return this.orders
-				}
-
-				return this.orders.filter((item) => item.type === this.activeTab)
+			photoPopupTitle() {
+				return this.photoPopupMode === 'deliver' ? '鎷嶇収閫佽揪' : '鎷嶇収鍙栬揣'
+			},
+			photoPopupLabel() {
+				return this.photoPopupMode === 'deliver' ? '鎷嶆憚閫佽揪鐓х墖' : '鎷嶆憚鍙栬揣鐓х墖'
+			},
+			photoPopupSubmitText() {
+				return this.photoPopupMode === 'deliver' ? '纭閫佽揪' : '纭鍙栬揣'
 			}
 		},
 		onLoad() {
 			const systemInfo = uni.getSystemInfoSync()
 			this.statusBarHeight = systemInfo.statusBarHeight || 0
 			this.navHeight = this.statusBarHeight + uni.upx2px(88)
+			this.getOrderList()
+		},
+		onShow() {
+			this.getActiveOrderCount()
+		},
+		onReachBottom() {
+			if (!this.hasMore || this.loading) return
+			this.page++
+			this.getOrderList()
+		},
+		watch: {
+			activeTab() {
+				this.page = 1
+				this.hasMore = true
+				this.orders = []
+				this.getOrderList()
+			}
 		},
 		methods: {
-			getBadgeIcon(badge) {
-				const badgeMap = {
-					'鏋侀�熻揪': '/static/image/ic_jisuda@2x.png',
-					'鏍囬�熻揪': '/static/image/ic_biaosuda@2x.png'
-				}
-
-				return badgeMap[badge] || ''
+			getActiveOrderCount() {
+				this.$u.api.activeOrderCount().then(res => {
+					if (res.code === 200) {
+						this.activeOrderCount = res.data
+					} else {
+						this.activeOrderCount = null
+					}
+				}).catch(() => {
+					this.activeOrderCount = null
+				})
 			},
-			goToOrderDetail(item) {
+			getBadgeIcon(item) {
+				return item.isUrgent ? '/static/image/ic_jisuda@2x.png' : '/static/image/ic_biaosuda@2x.png'
+			},
+			getStatusText(status) {
+				const map = {
+					2: '寰呮帴鍗�',
+					3: '寰呭彇璐�',
+					4: '閰嶉�佷腑',
+					5: '宸插畬鎴�',
+					7: '宸插畬鎴�',
+					99: '宸插彇娑�'
+				}
+				return map[status] || ''
+			},
+			getActions(item) {
+				if (item.status === 3) {
+					return [
+						{ text: '鍙栨秷璁㈠崟', type: 'light', fill: false },
+						{ text: '鍙栬揣鐮�', type: 'primary', fill: false },
+						{ text: '鎷嶇収鍙栬揣', type: 'primary', fill: true }
+					]
+				}
+				if (item.status === 4 && item.takeShopId) {
+					return [
+						{ text: '瀛樹欢鐮�', type: 'primary', fill: false }
+					]
+				}
+				if (item.status === 4 && !item.takeShopId) {
+					return [
+						{ text: '鎷嶇収閫佽揪', type: 'primary', fill: true }
+					]
+				}
+				return []
+			},
+			getOrderList() {
+				if (this.loading) return
+				this.loading = true
+				this.$u.api.orderPage({
+					capacity: 10,
+					page: this.page,
+					model: {
+						status: this.activeTab
+					}
+				}).then(res => {
+					if (res.code === 200) {
+						const list = res.data.records || []
+						if (this.page === 1) {
+							this.orders = list
+						} else {
+							this.orders = [...this.orders, ...list]
+						}
+						this.hasMore = list.length >= 10
+					}
+				}).finally(() => {
+					this.loading = false
+				})
+			},
+			goToOrderDetail(item, index) {
 				uni.navigateTo({
-					url: `/pages/order-detail/order-detail?id=${item.id}&status=${item.type === 'delivering' ? 'delivering' : item.type === 'finished' ? 'finished' : item.type === 'cancelled' ? 'cancelled' : item.type === 'rated' ? 'rated' : 'pickup'}`
+					url: `/pages/order-detail/order-detail?id=${item.id}`
+				})
+			},
+			handleAction(item, action) {
+				const text = action.text
+				if (text === '鍙栨秷璁㈠崟') {
+					this.handleCancelOrder(item)
+				} else if (text === '鍙栬揣鐮�' || text === '瀛樹欢鐮�') {
+					this.handleShowQRCode(item)
+				} else if (text === '鎷嶇収鍙栬揣') {
+					this.handlePhotoPickup(item)
+				} else if (text === '鎷嶇収閫佽揪') {
+					this.handlePhotoDeliver(item)
+				}
+			},
+			handleCancelOrder(item) {
+				this.selectedOrder = item
+				this.$u.api.cancelLimit().then(res => {
+					if (res.code === 200) {
+						this.cancelRemain = res.data.remain
+					}
+				}).finally(() => {
+					this.showCancelModal = true
+				})
+			},
+			confirmCancelOrder() {
+				if (!this.selectedOrder) return
+				this.$u.api.cancelOrder({ orderId: this.selectedOrder.id }).then(res => {
+					this.showCancelModal = false
+					if (res.code === 200) {
+						uni.showToast({ title: '鍙栨秷鎴愬姛', icon: 'success' })
+						this.getOrderList()
+						this.getActiveOrderCount()
+					} else {
+						uni.showToast({ title: res.message || '鍙栨秷澶辫触', icon: 'none' })
+					}
+				}).catch(() => {
+					this.showCancelModal = false
+				})
+			},
+			handleShowQRCode(item) {
+				this.selectedOrder = item
+				this.showQRPopup = true
+			},
+			handlePhotoPickup(item) {
+				this.selectedOrder = item
+				this.photoPopupMode = 'pickup'
+				this.uploadedPhotos = []
+				this.photoRemark = ''
+				this.showPhotoPopup = true
+			},
+			handlePhotoDeliver(item) {
+				this.selectedOrder = item
+				this.photoPopupMode = 'deliver'
+				this.uploadedPhotos = []
+				this.photoRemark = ''
+				this.showPhotoPopup = true
+			},
+			closePhotoPopup() {
+				this.showPhotoPopup = false
+			},
+			choosePhoto() {
+				if (this.uploadedPhotos.length >= 3) {
+					uni.showToast({ title: '鏈�澶氫笂浼�3寮犵収鐗�', icon: 'none' })
+					return
+				}
+				uni.chooseImage({
+					count: 3 - this.uploadedPhotos.length,
+					sourceType: ['camera', 'album'],
+					success: (res) => {
+						this.uploadedPhotos = [...this.uploadedPhotos, ...res.tempFilePaths]
+					}
+				})
+			},
+			deletePhoto(index) {
+				this.uploadedPhotos.splice(index, 1)
+			},
+			submitPhotoPopup() {
+				if (this.uploadedPhotos.length === 0) {
+					uni.showToast({ title: '璇蜂笂浼犵収鐗�', icon: 'none' })
+					return
+				}
+				uni.showLoading({ title: '涓婁紶涓�...' })
+				const uploadTasks = this.uploadedPhotos.map(path => {
+					return new Promise((resolve, reject) => {
+						uni.uploadFile({
+							url: this.$baseUrl + 'web/public/upload',
+							filePath: path,
+							name: 'file',
+							formData: {
+								folder: 'order'
+							},
+							success: (uploadRes) => {
+								const data = JSON.parse(uploadRes.data)
+								if (data.code === 200) {
+									resolve(data.data)
+								} else {
+									reject(new Error(data.msg))
+								}
+							},
+							fail: (err) => {
+								reject(err)
+							}
+						})
+					})
+				})
+
+				Promise.all(uploadTasks).then(images => {
+					const api = this.photoPopupMode === 'deliver' ? 'confirmDeliver' : 'confirmPickup'
+					const params = {
+						images: images.map(img => img.imgaddr),
+						orderId: this.selectedOrder.id,
+						remark: this.photoRemark
+					}
+					return this.$u.api[api](params)
+				}).then(res => {
+					uni.hideLoading()
+					if (res.code === 200) {
+						uni.showToast({ title: '鎻愪氦鎴愬姛', icon: 'success' })
+						this.showPhotoPopup = false
+						this.getOrderList()
+						this.getActiveOrderCount()
+					} else {
+						uni.showToast({ title: res.msg || '鎻愪氦澶辫触', icon: 'none' })
+					}
+				}).catch(err => {
+					uni.hideLoading()
+					uni.showToast({ title: err.message || '涓婁紶澶辫触', icon: 'none' })
 				})
 			}
 		}
@@ -325,6 +511,28 @@
 
 		&__list {
 			padding: 18rpx 22rpx calc(env(safe-area-inset-bottom) + 26rpx);
+		}
+
+		&__empty {
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			justify-content: center;
+			padding-top: 200rpx;
+		}
+
+		&__empty-icon {
+			width: 320rpx;
+			height: 320rpx;
+		}
+
+		&__loading {
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			padding: 30rpx 0;
+			color: #8f96a3;
+			font-size: 26rpx;
 		}
 	}
 
@@ -486,8 +694,7 @@
 
 		&__actions {
 			display: flex;
-			justify-content: flex-end;
-			gap: 20rpx;
+			justify-content: space-between;
 			margin-top: 18rpx;
 			padding-top: 18rpx;
 			border-top: 1rpx solid #f0f2f6;
@@ -529,4 +736,204 @@
 			}
 		}
 	}
+
+.qrcode {
+	padding: 30rpx 40rpx 50rpx;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+
+	&-title {
+		width: 100%;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		margin-bottom: 40rpx;
+
+		image {
+			width: 40rpx;
+			height: 40rpx;
+		}
+
+		text {
+			font-size: 34rpx;
+			font-weight: 700;
+			color: #2d3139;
+		}
+	}
+
+	&-image {
+		width: 400rpx;
+		height: 400rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		background: #f5f7fb;
+		border-radius: 16rpx;
+
+		image {
+			width: 360rpx;
+			height: 360rpx;
+		}
+	}
+
+	&-btn {
+		width: 100%;
+		height: 88rpx;
+		line-height: 88rpx;
+		text-align: center;
+		background: #106EFA;
+		border-radius: 44rpx;
+		font-size: 32rpx;
+		font-weight: 600;
+		color: #ffffff;
+		margin-top: 40rpx;
+
+		&--hover {
+			background: #0d5fc7;
+		}
+	}
+}
+
+.photo-popup {
+	padding: 30rpx 30rpx calc(env(safe-area-inset-bottom) + 30rpx);
+
+	&__header {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		margin-bottom: 40rpx;
+	}
+
+	&__placeholder {
+		width: 40rpx;
+		height: 40rpx;
+	}
+
+	&__title {
+		font-size: 34rpx;
+		font-weight: 700;
+		color: #2d3139;
+	}
+
+	&__close {
+		width: 40rpx;
+		height: 40rpx;
+	}
+
+	&__section {
+		margin-bottom: 30rpx;
+
+		&--remark {
+			margin-top: 30rpx;
+		}
+	}
+
+	&__label-row {
+		display: flex;
+		align-items: center;
+		margin-bottom: 20rpx;
+	}
+
+	&__label {
+		font-size: 30rpx;
+		font-weight: 600;
+		color: #2d3139;
+	}
+
+	&__required {
+		color: #ff4030;
+		margin-left: 8rpx;
+	}
+
+	&__hint {
+		font-size: 24rpx;
+		color: #8f96a3;
+		margin-left: auto;
+	}
+
+	&__photos {
+		display: flex;
+		flex-wrap: wrap;
+		gap: 20rpx;
+	}
+
+	&__preview-card {
+		position: relative;
+		width: 144rpx;
+		height: 144rpx;
+		border-radius: 12rpx;
+		overflow: hidden;
+	}
+
+	&__preview-image {
+		width: 100%;
+		height: 100%;
+	}
+
+	&__preview-mask {
+		position: absolute;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		height: 56rpx;
+		background: rgba(0, 0, 0, 0.5);
+		display: flex;
+		align-items: center;
+		justify-content: center;
+	}
+
+	&__preview-delete {
+		font-size: 26rpx;
+		color: #ffffff;
+	}
+
+	&__upload-btn {
+		width: 144rpx;
+		height: 144rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+
+		image {
+			width: 100%;
+			height: 100%;
+		}
+	}
+
+	&__remark-title {
+		font-size: 30rpx;
+		font-weight: 600;
+		color: #2d3139;
+		margin-bottom: 20rpx;
+		display: block;
+	}
+
+	&__textarea {
+		width: 100%;
+		height: 160rpx;
+		padding: 20rpx;
+		background: #f5f7fb;
+		border-radius: 12rpx;
+		font-size: 28rpx;
+		color: #2d3139;
+		box-sizing: border-box;
+	}
+
+	&__submit {
+		width: 100%;
+		height: 88rpx;
+		line-height: 88rpx;
+		background: #2c7cff;
+		border-radius: 44rpx;
+		font-size: 32rpx;
+		font-weight: 600;
+		color: #ffffff;
+		margin-top: 40rpx;
+
+		&--hover {
+			background: #2678e8;
+		}
+	}
+}
 </style>

--
Gitblit v1.9.3