|
|
@@ -0,0 +1,1164 @@
|
|
|
+<template>
|
|
|
+ <view class="page">
|
|
|
+ <NavBar title="签到记录" :showBack="true">
|
|
|
+ <template #right>
|
|
|
+ <view class="nav-action" @click="handleExport">
|
|
|
+ <view class="export-icon"></view>
|
|
|
+ </view>
|
|
|
+ </template>
|
|
|
+ </NavBar>
|
|
|
+
|
|
|
+ <view class="content">
|
|
|
+ <view class="filter-section">
|
|
|
+ <view class="filter-row">
|
|
|
+ <picker
|
|
|
+ mode="selector"
|
|
|
+ :range="typeOptions"
|
|
|
+ range-key="label"
|
|
|
+ @change="onTypeChange"
|
|
|
+ >
|
|
|
+ <view class="filter-item">
|
|
|
+ <text class="filter-label">{{ currentTypeLabel }}</text>
|
|
|
+ <view class="filter-arrow"></view>
|
|
|
+ </view>
|
|
|
+ </picker>
|
|
|
+
|
|
|
+ <picker
|
|
|
+ mode="date"
|
|
|
+ :value="filterStartDate"
|
|
|
+ @change="onStartDateChange"
|
|
|
+ >
|
|
|
+ <view class="filter-item">
|
|
|
+ <text class="filter-label">{{ filterStartDate || '开始日期' }}</text>
|
|
|
+ <view class="filter-arrow"></view>
|
|
|
+ </view>
|
|
|
+ </picker>
|
|
|
+
|
|
|
+ <picker
|
|
|
+ mode="date"
|
|
|
+ :value="filterEndDate"
|
|
|
+ @change="onEndDateChange"
|
|
|
+ >
|
|
|
+ <view class="filter-item">
|
|
|
+ <text class="filter-label">{{ filterEndDate || '结束日期' }}</text>
|
|
|
+ <view class="filter-arrow"></view>
|
|
|
+ </view>
|
|
|
+ </picker>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="stats-row">
|
|
|
+ <view class="stats-item">
|
|
|
+ <text class="stats-value">{{ stats.todayCount }}</text>
|
|
|
+ <text class="stats-label">今日签到</text>
|
|
|
+ </view>
|
|
|
+ <view class="stats-item">
|
|
|
+ <text class="stats-value">{{ stats.weekCount }}</text>
|
|
|
+ <text class="stats-label">本周签到</text>
|
|
|
+ </view>
|
|
|
+ <view class="stats-item">
|
|
|
+ <text class="stats-value">{{ stats.monthCount }}</text>
|
|
|
+ <text class="stats-label">本月签到</text>
|
|
|
+ </view>
|
|
|
+ <view class="stats-item warning" v-if="stats.pendingSyncCount > 0">
|
|
|
+ <text class="stats-value">{{ stats.pendingSyncCount }}</text>
|
|
|
+ <text class="stats-label">待同步</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="list-section">
|
|
|
+ <view class="list-header">
|
|
|
+ <text class="list-title">签到记录</text>
|
|
|
+ <text class="list-count">共 {{ total }} 条</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <scroll-view
|
|
|
+ class="list-scroll"
|
|
|
+ scroll-y
|
|
|
+ @scrolltolower="loadMore"
|
|
|
+ :refresher-enabled="true"
|
|
|
+ :refresher-triggered="isRefreshing"
|
|
|
+ @refresherrefresh="onRefresh"
|
|
|
+ >
|
|
|
+ <view class="record-list">
|
|
|
+ <view
|
|
|
+ class="record-item"
|
|
|
+ v-for="record in records"
|
|
|
+ :key="record.id"
|
|
|
+ @click="viewDetail(record)"
|
|
|
+ >
|
|
|
+ <view class="record-left">
|
|
|
+ <view class="record-type" :class="record.type">
|
|
|
+ <view class="type-icon"></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="record-content">
|
|
|
+ <view class="record-header">
|
|
|
+ <text class="record-type-name">{{ record.typeName }}</text>
|
|
|
+ <view class="record-status" :class="record.status">
|
|
|
+ <text>{{ getStatusText(record.status) }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="record-info">
|
|
|
+ <view class="info-row">
|
|
|
+ <view class="info-icon user"></view>
|
|
|
+ <text class="info-text">{{ record.userName }} ({{ record.userNo }})</text>
|
|
|
+ </view>
|
|
|
+ <view class="info-row">
|
|
|
+ <view class="info-icon location"></view>
|
|
|
+ <text class="info-text">{{ record.location.address }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="info-row">
|
|
|
+ <view class="info-icon time"></view>
|
|
|
+ <text class="info-text">{{ formatTime(record.createdAt) }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="record-photos" v-if="record.photos.length > 0">
|
|
|
+ <image
|
|
|
+ class="photo-thumb"
|
|
|
+ v-for="(photo, index) in record.photos.slice(0, 3)"
|
|
|
+ :key="photo.id"
|
|
|
+ :src="photo.path"
|
|
|
+ mode="aspectFill"
|
|
|
+ />
|
|
|
+ <view class="photo-more" v-if="record.photos.length > 3">
|
|
|
+ <text>+{{ record.photos.length - 3 }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="record-remark" v-if="record.remark">
|
|
|
+ <text>{{ record.remark }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="record-arrow">
|
|
|
+ <view class="arrow-icon"></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="empty-state" v-if="!isLoading && records.length === 0">
|
|
|
+ <view class="empty-icon"></view>
|
|
|
+ <text class="empty-text">暂无签到记录</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="loading-more" v-if="isLoadingMore">
|
|
|
+ <view class="loading-spinner"></view>
|
|
|
+ <text class="loading-text">加载中...</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="no-more" v-if="!hasMore && records.length > 0">
|
|
|
+ <text>没有更多了</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="detail-modal" v-if="showDetail && currentRecord">
|
|
|
+ <view class="detail-mask" @click="closeDetail"></view>
|
|
|
+ <view class="detail-content">
|
|
|
+ <view class="detail-header">
|
|
|
+ <text class="detail-title">签到详情</text>
|
|
|
+ <view class="detail-close" @click="closeDetail">
|
|
|
+ <view class="close-icon"></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <scroll-view class="detail-body" scroll-y>
|
|
|
+ <view class="detail-section">
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">签到类型</text>
|
|
|
+ <text class="detail-value">{{ currentRecord.typeName }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">签到人员</text>
|
|
|
+ <text class="detail-value">{{ currentRecord.userName }} ({{ currentRecord.userNo }})</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row">
|
|
|
+ <text class="detail-label">签到时间</text>
|
|
|
+ <text class="detail-value">{{ currentRecord.createdAt }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row" v-if="currentRecord.shopName">
|
|
|
+ <text class="detail-label">关联门店</text>
|
|
|
+ <text class="detail-value">{{ currentRecord.shopName }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="detail-row" v-if="currentRecord.deviceName">
|
|
|
+ <text class="detail-label">关联设备</text>
|
|
|
+ <text class="detail-value">{{ currentRecord.deviceName }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="detail-section">
|
|
|
+ <view class="section-title">位置信息</view>
|
|
|
+ <view class="location-map">
|
|
|
+ <view class="map-placeholder">
|
|
|
+ <view class="map-icon"></view>
|
|
|
+ <text class="map-text">{{ currentRecord.location.address }}</text>
|
|
|
+ <text class="map-coords">{{ currentRecord.location.latitude.toFixed(6) }}, {{ currentRecord.location.longitude.toFixed(6) }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="detail-section" v-if="currentRecord.photos.length > 0">
|
|
|
+ <view class="section-title">签到照片</view>
|
|
|
+ <view class="photo-grid">
|
|
|
+ <image
|
|
|
+ class="detail-photo"
|
|
|
+ v-for="photo in currentRecord.photos"
|
|
|
+ :key="photo.id"
|
|
|
+ :src="photo.path"
|
|
|
+ mode="aspectFill"
|
|
|
+ @click="previewPhotos(photo.path)"
|
|
|
+ />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="detail-section" v-if="currentRecord.remark">
|
|
|
+ <view class="section-title">备注信息</view>
|
|
|
+ <view class="remark-box">
|
|
|
+ <text>{{ currentRecord.remark }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="detail-section">
|
|
|
+ <view class="section-title">同步状态</view>
|
|
|
+ <view class="sync-info">
|
|
|
+ <view class="sync-status" :class="currentRecord.status">
|
|
|
+ <view class="status-dot"></view>
|
|
|
+ <text>{{ getStatusText(currentRecord.status) }}</text>
|
|
|
+ </view>
|
|
|
+ <text class="sync-time" v-if="currentRecord.syncedAt">
|
|
|
+ 同步时间: {{ currentRecord.syncedAt }}
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </scroll-view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { ref, computed, onMounted } from 'vue';
|
|
|
+import NavBar from '@/components/NavBar.vue';
|
|
|
+import { CheckinType, getCheckinList, getCheckinStats, exportCheckinRecords } from '@/api/checkin';
|
|
|
+import type { CheckinRecord, CheckinStats } from '@/api/checkin';
|
|
|
+
|
|
|
+const typeOptions = [
|
|
|
+ { value: '', label: '全部类型' },
|
|
|
+ { value: CheckinType.INVENTORY_TALLY, label: '理货盘点' },
|
|
|
+ { value: CheckinType.DELIVERY_REPLENISH, label: '补货确认' }
|
|
|
+];
|
|
|
+
|
|
|
+const filterType = ref('');
|
|
|
+const filterStartDate = ref('');
|
|
|
+const filterEndDate = ref('');
|
|
|
+const records = ref<CheckinRecord[]>([]);
|
|
|
+const total = ref(0);
|
|
|
+const page = ref(1);
|
|
|
+const pageSize = 10;
|
|
|
+const isLoading = ref(false);
|
|
|
+const isLoadingMore = ref(false);
|
|
|
+const isRefreshing = ref(false);
|
|
|
+const hasMore = ref(true);
|
|
|
+const showDetail = ref(false);
|
|
|
+const currentRecord = ref<CheckinRecord | null>(null);
|
|
|
+
|
|
|
+const stats = ref<CheckinStats>({
|
|
|
+ todayCount: 0,
|
|
|
+ weekCount: 0,
|
|
|
+ monthCount: 0,
|
|
|
+ inventoryTallyCount: 0,
|
|
|
+ deliveryReplenishCount: 0,
|
|
|
+ pendingSyncCount: 0
|
|
|
+});
|
|
|
+
|
|
|
+const currentTypeLabel = computed(() => {
|
|
|
+ const option = typeOptions.find(o => o.value === filterType.value);
|
|
|
+ return option ? option.label : '全部类型';
|
|
|
+});
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ loadData();
|
|
|
+ loadStats();
|
|
|
+});
|
|
|
+
|
|
|
+const loadData = async (reset = false) => {
|
|
|
+ if (reset) {
|
|
|
+ page.value = 1;
|
|
|
+ hasMore.value = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isLoading.value) return;
|
|
|
+ isLoading.value = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const result = await getCheckinList({
|
|
|
+ page: page.value,
|
|
|
+ pageSize,
|
|
|
+ type: filterType.value as CheckinType || undefined,
|
|
|
+ startDate: filterStartDate.value || undefined,
|
|
|
+ endDate: filterEndDate.value || undefined
|
|
|
+ });
|
|
|
+
|
|
|
+ if (reset) {
|
|
|
+ records.value = result.list;
|
|
|
+ } else {
|
|
|
+ records.value = [...records.value, ...result.list];
|
|
|
+ }
|
|
|
+
|
|
|
+ total.value = result.total;
|
|
|
+ hasMore.value = records.value.length < result.total;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载签到记录失败', error);
|
|
|
+ } finally {
|
|
|
+ isLoading.value = false;
|
|
|
+ isRefreshing.value = false;
|
|
|
+ isLoadingMore.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const loadStats = async () => {
|
|
|
+ try {
|
|
|
+ stats.value = await getCheckinStats();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载统计数据失败', error);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const loadMore = () => {
|
|
|
+ if (!hasMore.value || isLoadingMore.value) return;
|
|
|
+
|
|
|
+ isLoadingMore.value = true;
|
|
|
+ page.value++;
|
|
|
+ loadData();
|
|
|
+};
|
|
|
+
|
|
|
+const onRefresh = () => {
|
|
|
+ isRefreshing.value = true;
|
|
|
+ loadData(true);
|
|
|
+ loadStats();
|
|
|
+};
|
|
|
+
|
|
|
+const onTypeChange = (e: any) => {
|
|
|
+ filterType.value = typeOptions[e.detail.value].value;
|
|
|
+ loadData(true);
|
|
|
+};
|
|
|
+
|
|
|
+const onStartDateChange = (e: any) => {
|
|
|
+ filterStartDate.value = e.detail.value;
|
|
|
+ loadData(true);
|
|
|
+};
|
|
|
+
|
|
|
+const onEndDateChange = (e: any) => {
|
|
|
+ filterEndDate.value = e.detail.value;
|
|
|
+ loadData(true);
|
|
|
+};
|
|
|
+
|
|
|
+const getStatusText = (status: string) => {
|
|
|
+ const statusMap: Record<string, string> = {
|
|
|
+ pending: '待同步',
|
|
|
+ synced: '已同步',
|
|
|
+ failed: '同步失败'
|
|
|
+ };
|
|
|
+ return statusMap[status] || status;
|
|
|
+};
|
|
|
+
|
|
|
+const formatTime = (timeStr: string) => {
|
|
|
+ return timeStr.replace('T', ' ').substring(0, 19);
|
|
|
+};
|
|
|
+
|
|
|
+const viewDetail = (record: CheckinRecord) => {
|
|
|
+ currentRecord.value = record;
|
|
|
+ showDetail.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const closeDetail = () => {
|
|
|
+ showDetail.value = false;
|
|
|
+ currentRecord.value = null;
|
|
|
+};
|
|
|
+
|
|
|
+const previewPhotos = (current: string) => {
|
|
|
+ if (!currentRecord.value) return;
|
|
|
+
|
|
|
+ uni.previewImage({
|
|
|
+ urls: currentRecord.value.photos.map(p => p.path),
|
|
|
+ current
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const handleExport = async () => {
|
|
|
+ uni.showLoading({ title: '导出中...' });
|
|
|
+
|
|
|
+ try {
|
|
|
+ const url = await exportCheckinRecords({
|
|
|
+ type: filterType.value as CheckinType || undefined,
|
|
|
+ startDate: filterStartDate.value || undefined,
|
|
|
+ endDate: filterEndDate.value || undefined
|
|
|
+ });
|
|
|
+
|
|
|
+ uni.hideLoading();
|
|
|
+
|
|
|
+ // #ifdef H5
|
|
|
+ window.open(url, '_blank');
|
|
|
+ // #endif
|
|
|
+
|
|
|
+ // #ifndef H5
|
|
|
+ uni.showToast({
|
|
|
+ title: '导出成功',
|
|
|
+ icon: 'success'
|
|
|
+ });
|
|
|
+ // #endif
|
|
|
+ } catch (error) {
|
|
|
+ uni.hideLoading();
|
|
|
+ uni.showToast({
|
|
|
+ title: '导出失败',
|
|
|
+ icon: 'none'
|
|
|
+ });
|
|
|
+ }
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.page {
|
|
|
+ min-height: 100vh;
|
|
|
+ background: #f8fafc;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.nav-action {
|
|
|
+ width: 64rpx;
|
|
|
+ height: 64rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+
|
|
|
+.export-icon {
|
|
|
+ width: 36rpx;
|
|
|
+ height: 36rpx;
|
|
|
+ border: 3rpx solid #1e293b;
|
|
|
+ border-radius: 6rpx;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -30%);
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border-left: 6rpx solid transparent;
|
|
|
+ border-right: 6rpx solid transparent;
|
|
|
+ border-bottom: 8rpx solid #1e293b;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ bottom: 4rpx;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ width: 12rpx;
|
|
|
+ height: 3rpx;
|
|
|
+ background: #1e293b;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.content {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-section {
|
|
|
+ padding: 16rpx 24rpx;
|
|
|
+ background: #ffffff;
|
|
|
+ border-bottom: 1rpx solid #e2e8f0;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-row {
|
|
|
+ display: flex;
|
|
|
+ gap: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-item {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ height: 64rpx;
|
|
|
+ background: #f8fafc;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ padding: 0 16rpx;
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ background: #f1f5f9;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.filter-label {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #475569;
|
|
|
+ margin-right: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-arrow {
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border-left: 8rpx solid transparent;
|
|
|
+ border-right: 8rpx solid transparent;
|
|
|
+ border-top: 8rpx solid #94a3b8;
|
|
|
+}
|
|
|
+
|
|
|
+.stats-row {
|
|
|
+ display: flex;
|
|
|
+ padding: 20rpx 24rpx;
|
|
|
+ background: #ffffff;
|
|
|
+ border-bottom: 1rpx solid #e2e8f0;
|
|
|
+}
|
|
|
+
|
|
|
+.stats-item {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ &.warning {
|
|
|
+ .stats-value {
|
|
|
+ color: #f59e0b;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.stats-value {
|
|
|
+ font-size: 40rpx;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1e293b;
|
|
|
+ margin-bottom: 4rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.stats-label {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #64748b;
|
|
|
+}
|
|
|
+
|
|
|
+.list-section {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.list-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 20rpx 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.list-title {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1e293b;
|
|
|
+}
|
|
|
+
|
|
|
+.list-count {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #94a3b8;
|
|
|
+}
|
|
|
+
|
|
|
+.list-scroll {
|
|
|
+ flex: 1;
|
|
|
+ height: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.record-list {
|
|
|
+ padding: 0 24rpx;
|
|
|
+ padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
|
|
+}
|
|
|
+
|
|
|
+.record-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ background: #ffffff;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 24rpx;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+ border: 1rpx solid #e2e8f0;
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ background: #f8fafc;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.record-left {
|
|
|
+ margin-right: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.record-type {
|
|
|
+ width: 72rpx;
|
|
|
+ height: 72rpx;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+
|
|
|
+ &.inventory_tally {
|
|
|
+ background: #ecfdf5;
|
|
|
+
|
|
|
+ .type-icon {
|
|
|
+ width: 28rpx;
|
|
|
+ height: 36rpx;
|
|
|
+ border: 3rpx solid #10b981;
|
|
|
+ border-radius: 4rpx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.delivery_replenish {
|
|
|
+ background: #fff7ed;
|
|
|
+
|
|
|
+ .type-icon {
|
|
|
+ width: 32rpx;
|
|
|
+ height: 24rpx;
|
|
|
+ border: 3rpx solid #f97316;
|
|
|
+ border-radius: 4rpx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.record-content {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.record-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.record-type-name {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1e293b;
|
|
|
+}
|
|
|
+
|
|
|
+.record-status {
|
|
|
+ padding: 4rpx 12rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ font-size: 22rpx;
|
|
|
+
|
|
|
+ &.synced {
|
|
|
+ background: #ecfdf5;
|
|
|
+ color: #10b981;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.pending {
|
|
|
+ background: #fffbeb;
|
|
|
+ color: #f59e0b;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.failed {
|
|
|
+ background: #fef2f2;
|
|
|
+ color: #ef4444;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.record-info {
|
|
|
+ margin-bottom: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.info-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.info-icon {
|
|
|
+ width: 28rpx;
|
|
|
+ height: 28rpx;
|
|
|
+ margin-right: 8rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+
|
|
|
+ &.user {
|
|
|
+ background: #e0f2fe;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 6rpx;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ width: 8rpx;
|
|
|
+ height: 8rpx;
|
|
|
+ background: #0ea5e9;
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ bottom: 6rpx;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ width: 14rpx;
|
|
|
+ height: 6rpx;
|
|
|
+ background: #0ea5e9;
|
|
|
+ border-radius: 6rpx 6rpx 0 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.location {
|
|
|
+ background: #ecfdf5;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ width: 8rpx;
|
|
|
+ height: 8rpx;
|
|
|
+ background: #10b981;
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ width: 14rpx;
|
|
|
+ height: 14rpx;
|
|
|
+ border: 2rpx solid #10b981;
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.time {
|
|
|
+ background: #faf5ff;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ width: 12rpx;
|
|
|
+ height: 12rpx;
|
|
|
+ border: 2rpx solid #a855f7;
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 8rpx;
|
|
|
+ left: 50%;
|
|
|
+ transform: translateX(-50%);
|
|
|
+ width: 2rpx;
|
|
|
+ height: 6rpx;
|
|
|
+ background: #a855f7;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.info-text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #64748b;
|
|
|
+ flex: 1;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.record-photos {
|
|
|
+ display: flex;
|
|
|
+ gap: 8rpx;
|
|
|
+ margin-bottom: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.photo-thumb {
|
|
|
+ width: 80rpx;
|
|
|
+ height: 80rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ background: #f1f5f9;
|
|
|
+}
|
|
|
+
|
|
|
+.photo-more {
|
|
|
+ width: 80rpx;
|
|
|
+ height: 80rpx;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ background: rgba(0, 0, 0, 0.5);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+
|
|
|
+ text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #ffffff;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.record-remark {
|
|
|
+ padding: 12rpx;
|
|
|
+ background: #f8fafc;
|
|
|
+ border-radius: 8rpx;
|
|
|
+
|
|
|
+ text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #64748b;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.record-arrow {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding-left: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.arrow-icon {
|
|
|
+ width: 12rpx;
|
|
|
+ height: 12rpx;
|
|
|
+ border-top: 3rpx solid #cbd5e1;
|
|
|
+ border-right: 3rpx solid #cbd5e1;
|
|
|
+ transform: rotate(45deg);
|
|
|
+}
|
|
|
+
|
|
|
+.empty-state {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ padding: 100rpx 0;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-icon {
|
|
|
+ width: 120rpx;
|
|
|
+ height: 120rpx;
|
|
|
+ background: #f1f5f9;
|
|
|
+ border-radius: 50%;
|
|
|
+ margin-bottom: 24rpx;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ width: 40rpx;
|
|
|
+ height: 40rpx;
|
|
|
+ border: 4rpx solid #cbd5e1;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.empty-text {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #94a3b8;
|
|
|
+}
|
|
|
+
|
|
|
+.loading-more {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.loading-spinner {
|
|
|
+ width: 32rpx;
|
|
|
+ height: 32rpx;
|
|
|
+ border: 3rpx solid #e2e8f0;
|
|
|
+ border-top-color: #10b981;
|
|
|
+ border-radius: 50%;
|
|
|
+ animation: rotate 0.8s linear infinite;
|
|
|
+
|
|
|
+ @keyframes rotate {
|
|
|
+ from { transform: rotate(0deg); }
|
|
|
+ to { transform: rotate(360deg); }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.loading-text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #94a3b8;
|
|
|
+ margin-left: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.no-more {
|
|
|
+ text-align: center;
|
|
|
+ padding: 24rpx;
|
|
|
+
|
|
|
+ text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #94a3b8;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.detail-modal {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ z-index: 1000;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-mask {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: rgba(0, 0, 0, 0.5);
|
|
|
+}
|
|
|
+
|
|
|
+.detail-content {
|
|
|
+ position: absolute;
|
|
|
+ bottom: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ max-height: 80vh;
|
|
|
+ background: #ffffff;
|
|
|
+ border-radius: 32rpx 32rpx 0 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 32rpx;
|
|
|
+ border-bottom: 1rpx solid #e2e8f0;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-title {
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1e293b;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-close {
|
|
|
+ width: 56rpx;
|
|
|
+ height: 56rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: #f1f5f9;
|
|
|
+ border-radius: 50%;
|
|
|
+}
|
|
|
+
|
|
|
+.close-icon {
|
|
|
+ width: 20rpx;
|
|
|
+ height: 20rpx;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ &::before, &::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ width: 24rpx;
|
|
|
+ height: 3rpx;
|
|
|
+ background: #64748b;
|
|
|
+ border-radius: 2rpx;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ transform: translate(-50%, -50%) rotate(45deg);
|
|
|
+ }
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ transform: translate(-50%, -50%) rotate(-45deg);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.detail-body {
|
|
|
+ flex: 1;
|
|
|
+ padding: 24rpx 32rpx;
|
|
|
+ padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
|
|
+}
|
|
|
+
|
|
|
+.detail-section {
|
|
|
+ margin-bottom: 32rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-row {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding: 16rpx 0;
|
|
|
+ border-bottom: 1rpx solid #f1f5f9;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ border-bottom: none;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.detail-label {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #64748b;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-value {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #1e293b;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.section-title {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1e293b;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.location-map {
|
|
|
+ background: #f8fafc;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.map-placeholder {
|
|
|
+ padding: 32rpx;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.map-icon {
|
|
|
+ width: 48rpx;
|
|
|
+ height: 48rpx;
|
|
|
+ background: #10b981;
|
|
|
+ border-radius: 50%;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ width: 16rpx;
|
|
|
+ height: 16rpx;
|
|
|
+ background: #ffffff;
|
|
|
+ border-radius: 50%;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.map-text {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #1e293b;
|
|
|
+ text-align: center;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.map-coords {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #94a3b8;
|
|
|
+}
|
|
|
+
|
|
|
+.photo-grid {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-photo {
|
|
|
+ width: calc(33.33% - 8rpx);
|
|
|
+ aspect-ratio: 1;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ background: #f1f5f9;
|
|
|
+}
|
|
|
+
|
|
|
+.remark-box {
|
|
|
+ padding: 20rpx;
|
|
|
+ background: #f8fafc;
|
|
|
+ border-radius: 12rpx;
|
|
|
+
|
|
|
+ text {
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #475569;
|
|
|
+ line-height: 1.6;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.sync-info {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.sync-status {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ padding: 8rpx 16rpx;
|
|
|
+ border-radius: 12rpx;
|
|
|
+
|
|
|
+ &.synced {
|
|
|
+ background: #ecfdf5;
|
|
|
+
|
|
|
+ .status-dot {
|
|
|
+ background: #10b981;
|
|
|
+ }
|
|
|
+
|
|
|
+ text {
|
|
|
+ color: #10b981;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.pending {
|
|
|
+ background: #fffbeb;
|
|
|
+
|
|
|
+ .status-dot {
|
|
|
+ background: #f59e0b;
|
|
|
+ }
|
|
|
+
|
|
|
+ text {
|
|
|
+ color: #f59e0b;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.failed {
|
|
|
+ background: #fef2f2;
|
|
|
+
|
|
|
+ .status-dot {
|
|
|
+ background: #ef4444;
|
|
|
+ }
|
|
|
+
|
|
|
+ text {
|
|
|
+ color: #ef4444;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.status-dot {
|
|
|
+ width: 12rpx;
|
|
|
+ height: 12rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ margin-right: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.sync-time {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #94a3b8;
|
|
|
+ margin-left: 16rpx;
|
|
|
+}
|
|
|
+</style>
|