2025-12-18 16:49:44 +08:00
|
|
|
|
<script setup lang="ts">
|
2025-12-19 11:14:02 +08:00
|
|
|
|
import { availableStorageCells, storeItemApi, openByPassword, resetByPassword } from '@/api/cabinet/index'
|
2025-12-18 16:49:44 +08:00
|
|
|
|
import type { AvailableStorageCellDTO } from '@/api/cabinet/types'
|
|
|
|
|
|
import { ref, computed, onMounted } from 'vue'
|
2025-12-19 11:14:02 +08:00
|
|
|
|
import { useMessage } from 'wot-design-uni'
|
2025-12-18 16:49:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 格口类型映射
|
|
|
|
|
|
const CELL_TYPE_MAP = {
|
|
|
|
|
|
1: '小格',
|
|
|
|
|
|
2: '中格',
|
|
|
|
|
|
3: '大格',
|
|
|
|
|
|
4: '超大格'
|
|
|
|
|
|
} as const
|
|
|
|
|
|
|
|
|
|
|
|
// 组件 Props
|
|
|
|
|
|
interface Props {
|
|
|
|
|
|
/** 店铺ID,用于获取可用格口列表 */
|
|
|
|
|
|
shopId: number
|
|
|
|
|
|
/** 是否自动加载数据 */
|
|
|
|
|
|
autoLoad?: boolean
|
|
|
|
|
|
/** 是否显示操作按钮 */
|
|
|
|
|
|
showButtons?: boolean
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
|
|
|
|
autoLoad: true,
|
|
|
|
|
|
showButtons: true
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 组件 Emits
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
|
(e: 'deposit'): void
|
|
|
|
|
|
(e: 'retrieve'): void
|
|
|
|
|
|
(e: 'refresh'): void
|
|
|
|
|
|
(e: 'error', error: Error): void
|
2025-12-19 11:14:02 +08:00
|
|
|
|
(e: 'backToAddressSelect'): void
|
2025-12-18 16:49:44 +08:00
|
|
|
|
}>()
|
|
|
|
|
|
|
|
|
|
|
|
// 状态变量
|
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
const error = ref<Error | null>(null)
|
|
|
|
|
|
const cellsData = ref<AvailableStorageCellDTO[]>([])
|
|
|
|
|
|
const selectedCellType = ref<number>(1)
|
|
|
|
|
|
|
2025-12-19 11:14:02 +08:00
|
|
|
|
const message = useMessage()
|
|
|
|
|
|
const depositLoading = ref(false)
|
|
|
|
|
|
const retrieveLoading = ref(false)
|
|
|
|
|
|
const generatedPassword = ref('')
|
|
|
|
|
|
|
2025-12-18 16:49:44 +08:00
|
|
|
|
// 统计计算属性
|
|
|
|
|
|
const availableCells = computed(() =>
|
|
|
|
|
|
cellsData.value.filter(cell => !cell.hasPassword)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const cellTypeStats = computed(() => {
|
|
|
|
|
|
const stats = new Map<number, number>()
|
|
|
|
|
|
|
|
|
|
|
|
availableCells.value.forEach(cell => {
|
|
|
|
|
|
const type = cell.cellType
|
|
|
|
|
|
stats.set(type, (stats.get(type) || 0) + 1)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 确保所有四种类型都包含,即使数量为0
|
|
|
|
|
|
const allTypes = [1, 2, 3, 4] as const
|
|
|
|
|
|
return allTypes.map(type => ({
|
|
|
|
|
|
type,
|
|
|
|
|
|
name: CELL_TYPE_MAP[type as keyof typeof CELL_TYPE_MAP] || '未知',
|
|
|
|
|
|
count: stats.get(type) || 0
|
|
|
|
|
|
}))
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const hasAvailableCells = computed(() =>
|
|
|
|
|
|
availableCells.value.length > 0
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 数据获取逻辑
|
|
|
|
|
|
async function refresh() {
|
|
|
|
|
|
loading.value = true
|
|
|
|
|
|
error.value = null
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await availableStorageCells(props.shopId)
|
|
|
|
|
|
cellsData.value = response.data || []
|
|
|
|
|
|
emit('refresh')
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
error.value = err as Error
|
|
|
|
|
|
emit('error', err as Error)
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '数据加载失败',
|
|
|
|
|
|
icon: 'error'
|
|
|
|
|
|
})
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 事件处理函数
|
|
|
|
|
|
function handleDeposit() {
|
2025-12-19 11:14:02 +08:00
|
|
|
|
// 保持事件发射以向后兼容
|
2025-12-18 16:49:44 +08:00
|
|
|
|
emit('deposit')
|
2025-12-19 11:14:02 +08:00
|
|
|
|
// 调用新的存入流程函数
|
|
|
|
|
|
handleDepositFlow()
|
2025-12-18 16:49:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleRetrieve() {
|
2025-12-19 11:14:02 +08:00
|
|
|
|
// 保持事件发射以向后兼容
|
2025-12-18 16:49:44 +08:00
|
|
|
|
emit('retrieve')
|
2025-12-19 11:14:02 +08:00
|
|
|
|
// 调用新的取出流程函数
|
|
|
|
|
|
handleRetrieveFlow()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleBackToAddressSelect() {
|
|
|
|
|
|
emit('backToAddressSelect')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 存入流程处理函数
|
|
|
|
|
|
async function handleDepositFlow() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 1. 调用 storeItemApi 分配格口
|
|
|
|
|
|
depositLoading.value = true
|
|
|
|
|
|
const response = await storeItemApi({
|
|
|
|
|
|
shopId: props.shopId,
|
|
|
|
|
|
cellType: selectedCellType.value
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 保存生成的密码(根据用户确认,接口返回包含 password 字段)
|
|
|
|
|
|
generatedPassword.value = response.data?.password || ''
|
|
|
|
|
|
|
|
|
|
|
|
// 检查密码是否为空
|
|
|
|
|
|
if (!generatedPassword.value) {
|
|
|
|
|
|
throw new Error('格口分配失败,未获取到密码')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 显示密码弹窗让用户记住
|
|
|
|
|
|
await message.alert({
|
|
|
|
|
|
title: '密码已生成',
|
|
|
|
|
|
msg: `请牢记你的暂存密码为:${generatedPassword.value}\n请确认已打开的柜子,放置物品后将柜子关闭。`,
|
|
|
|
|
|
confirmButtonText: '已记住',
|
|
|
|
|
|
closeOnClickModal: false
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 密码验证弹窗
|
|
|
|
|
|
const { value: inputPassword } = await message.prompt({
|
|
|
|
|
|
title: '密码验证',
|
|
|
|
|
|
msg: '请输入刚才显示的密码进行验证',
|
|
|
|
|
|
inputPlaceholder: '请输入密码',
|
|
|
|
|
|
closeOnClickModal: false,
|
|
|
|
|
|
inputValidate: ((value: string) => {
|
|
|
|
|
|
if (!value) return '请输入密码'
|
|
|
|
|
|
if (value !== generatedPassword.value) return '密码不正确'
|
|
|
|
|
|
return true
|
|
|
|
|
|
}) as any
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 打开格口
|
|
|
|
|
|
await openByPassword({
|
|
|
|
|
|
shopId: props.shopId,
|
|
|
|
|
|
password: String(inputPassword)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 成功提示
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '格口已打开',
|
|
|
|
|
|
icon: 'success'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 刷新数据
|
|
|
|
|
|
await refresh()
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// 处理不同类型的错误
|
|
|
|
|
|
// 用户取消操作(message.prompt/confirm/alert 返回 'cancel' 字符串或包含 cancel 的错误)
|
|
|
|
|
|
if (error === 'cancel' || (error as any)?.message?.includes?.('cancel')) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// API 错误处理
|
|
|
|
|
|
const errorMessage = (error as any)?.message || '操作失败'
|
|
|
|
|
|
console.error('存入流程失败:', error)
|
|
|
|
|
|
|
|
|
|
|
|
// 显示具体的错误信息
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: errorMessage.length > 20 ? '操作失败' : errorMessage,
|
|
|
|
|
|
icon: 'error',
|
|
|
|
|
|
duration: 3000
|
|
|
|
|
|
})
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
depositLoading.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 取出流程处理函数
|
|
|
|
|
|
async function handleRetrieveFlow() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 1. 确认取出弹窗
|
|
|
|
|
|
// 注释掉确认弹窗,直接执行取出操作
|
|
|
|
|
|
/* await message.confirm({
|
|
|
|
|
|
title: '物品取出',
|
|
|
|
|
|
msg: '确认要取出物品吗?',
|
|
|
|
|
|
closeOnClickModal: false
|
|
|
|
|
|
}) */
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 密码输入弹窗
|
|
|
|
|
|
const { value: password } = await message.prompt({
|
|
|
|
|
|
title: '输入密码',
|
|
|
|
|
|
msg: '请输入格口密码',
|
|
|
|
|
|
inputPlaceholder: '请输入密码',
|
|
|
|
|
|
closeOnClickModal: false,
|
|
|
|
|
|
inputType: ('password') as any
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 打开格口
|
|
|
|
|
|
retrieveLoading.value = true
|
|
|
|
|
|
await openByPassword({
|
|
|
|
|
|
shopId: props.shopId,
|
|
|
|
|
|
password: String(password)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-12-20 15:17:15 +08:00
|
|
|
|
// 4. 成功提示
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '格口已打开',
|
|
|
|
|
|
icon: 'success'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 注释掉清空格口部分,打开格口即显示打开成功结束流程
|
|
|
|
|
|
/*
|
|
|
|
|
|
// 成功提示并询问是否清空
|
2025-12-19 11:14:02 +08:00
|
|
|
|
await message.alert({
|
|
|
|
|
|
title: '格口已打开',
|
2025-12-20 15:17:15 +08:00
|
|
|
|
msg: '柜子已开,请取物品!如不再使用柜子请点击 "清空" 。',
|
2025-12-19 11:14:02 +08:00
|
|
|
|
confirmButtonText: '清空',
|
|
|
|
|
|
closeOnClickModal: false
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-12-20 15:17:15 +08:00
|
|
|
|
// 确认清空弹窗
|
2025-12-19 11:14:02 +08:00
|
|
|
|
await message.confirm({
|
|
|
|
|
|
title: '确认清空',
|
|
|
|
|
|
msg: '清空后密码将不能再次使用,确认清空?',
|
|
|
|
|
|
closeOnClickModal: false
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-12-20 15:17:15 +08:00
|
|
|
|
// 重置格口状态
|
2025-12-19 11:14:02 +08:00
|
|
|
|
await resetByPassword({
|
|
|
|
|
|
shopId: props.shopId,
|
|
|
|
|
|
password: String(password)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-12-20 15:17:15 +08:00
|
|
|
|
// 重置成功提示
|
2025-12-19 11:14:02 +08:00
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '格口已清空',
|
|
|
|
|
|
icon: 'success'
|
|
|
|
|
|
})
|
2025-12-20 15:17:15 +08:00
|
|
|
|
*/
|
2025-12-19 11:14:02 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// 错误处理
|
|
|
|
|
|
// 用户取消操作(message.prompt/confirm/alert 返回 'cancel' 字符串或包含 cancel 的错误)
|
|
|
|
|
|
if (error === 'cancel' || (error as any)?.message?.includes?.('cancel')) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// API 错误处理
|
|
|
|
|
|
const errorMessage = (error as any)?.message || '操作失败'
|
|
|
|
|
|
console.error('取出流程失败:', error)
|
|
|
|
|
|
|
|
|
|
|
|
// 显示具体的错误信息
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: errorMessage.length > 20 ? '操作失败' : errorMessage,
|
|
|
|
|
|
icon: 'error',
|
|
|
|
|
|
duration: 3000
|
|
|
|
|
|
})
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
retrieveLoading.value = false
|
|
|
|
|
|
await refresh()
|
|
|
|
|
|
}
|
2025-12-18 16:49:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-20 15:17:15 +08:00
|
|
|
|
// 格口类型图标映射(需验证图标名称可用性)
|
|
|
|
|
|
const CELL_TYPE_ICON_MAP = {
|
|
|
|
|
|
1: 'box', // 小格
|
|
|
|
|
|
2: 'archive', // 中格
|
|
|
|
|
|
3: 'package', // 大格
|
|
|
|
|
|
4: 'cube' // 超大格
|
|
|
|
|
|
} as const
|
|
|
|
|
|
|
|
|
|
|
|
function getCellImg(type: number): string {
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
return '/static/svg/small-cell.svg'
|
|
|
|
|
|
case 2:
|
|
|
|
|
|
return '/static/svg/medium-cell.svg'
|
|
|
|
|
|
case 3:
|
|
|
|
|
|
return '/static/svg/large-cell.svg'
|
|
|
|
|
|
case 4:
|
|
|
|
|
|
return '/static/svg/extra-large-cell.svg'
|
|
|
|
|
|
default:
|
|
|
|
|
|
return '/static/svg/small-cell.svg'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理格口类型选择
|
|
|
|
|
|
function handleCellTypeSelect(stat: { type: number; count: number }) {
|
|
|
|
|
|
if (stat.count === 0) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '该类型格口暂不可用',
|
|
|
|
|
|
icon: 'none',
|
|
|
|
|
|
duration: 1500
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
selectedCellType.value = stat.type
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-18 16:49:44 +08:00
|
|
|
|
// 生命周期钩子
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
if (props.autoLoad !== false) {
|
|
|
|
|
|
refresh()
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<view class="storage-cells-summary">
|
|
|
|
|
|
<!-- 标题区域 -->
|
|
|
|
|
|
<view class="summary-header">
|
|
|
|
|
|
<text class="header-title">本区域空余暂存柜子</text>
|
2025-12-19 11:14:02 +08:00
|
|
|
|
<view class="header-actions">
|
|
|
|
|
|
<view class="back-btn" @click="handleBackToAddressSelect">
|
|
|
|
|
|
<wd-icon name="arrow-left" size="12px"></wd-icon>
|
|
|
|
|
|
<text style="margin-left: 4px">重选地址</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<wd-button v-if="!loading" size="small" type="primary" plain @click="refresh">
|
|
|
|
|
|
刷新
|
|
|
|
|
|
</wd-button>
|
|
|
|
|
|
</view>
|
2025-12-18 16:49:44 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 加载状态 -->
|
|
|
|
|
|
<view v-if="loading" class="loading-state">
|
|
|
|
|
|
<wd-loading type="ring" />
|
|
|
|
|
|
<text class="loading-text">正在加载格口数据...</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 错误状态 -->
|
|
|
|
|
|
<view v-else-if="error" class="error-state">
|
|
|
|
|
|
<wd-icon name="warning" size="48px" color="#F56C6C" />
|
|
|
|
|
|
<text class="error-text">数据加载失败</text>
|
|
|
|
|
|
<wd-button size="small" type="primary" @click="refresh">重试</wd-button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 空状态 -->
|
|
|
|
|
|
<view v-else-if="!hasAvailableCells" class="empty-state">
|
|
|
|
|
|
<wd-icon name="box" size="48px" color="#C0C4CC" />
|
|
|
|
|
|
<text class="empty-text">暂无可用格口</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 格口类型选择区域 -->
|
|
|
|
|
|
<view v-else class="cell-type-selection">
|
2025-12-20 15:17:15 +08:00
|
|
|
|
<view class="cell-type-grid">
|
|
|
|
|
|
<view
|
|
|
|
|
|
v-for="stat in cellTypeStats"
|
|
|
|
|
|
:key="stat.type"
|
|
|
|
|
|
class="cell-type-card"
|
|
|
|
|
|
:class="{
|
|
|
|
|
|
'cell-type-card--selected': selectedCellType === stat.type,
|
|
|
|
|
|
'cell-type-card--disabled': stat.count === 0
|
|
|
|
|
|
}"
|
|
|
|
|
|
@click="handleCellTypeSelect(stat)"
|
2025-12-18 16:49:44 +08:00
|
|
|
|
>
|
2025-12-20 15:17:15 +08:00
|
|
|
|
<view class="cell-type-icon">
|
|
|
|
|
|
<image :src="getCellImg(stat.type)" class="product-image">
|
|
|
|
|
|
</image>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<text class="cell-type-name">{{ stat.name }}</text>
|
|
|
|
|
|
<text class="cell-type-count">剩余{{ stat.count }}个可用</text>
|
|
|
|
|
|
<!-- <view v-if="selectedCellType === stat.type" class="cell-type-selected-indicator">
|
|
|
|
|
|
<wd-icon name="check" size="16px" color="#fff" />
|
|
|
|
|
|
</view> -->
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
2025-12-18 16:49:44 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 操作按钮区域 -->
|
|
|
|
|
|
<view v-if="props.showButtons && hasAvailableCells && !loading" class="action-buttons">
|
|
|
|
|
|
<wd-row :gutter="16">
|
|
|
|
|
|
<wd-col :span="12">
|
2025-12-19 11:14:02 +08:00
|
|
|
|
<wd-button type="primary" block @click="handleDeposit" :loading="depositLoading" :disabled="depositLoading || retrieveLoading">物品暂存</wd-button>
|
2025-12-18 16:49:44 +08:00
|
|
|
|
</wd-col>
|
|
|
|
|
|
<wd-col :span="12">
|
2025-12-19 11:14:02 +08:00
|
|
|
|
<wd-button type="success" block @click="handleRetrieve" :loading="retrieveLoading" :disabled="depositLoading || retrieveLoading">物品取出</wd-button>
|
2025-12-18 16:49:44 +08:00
|
|
|
|
</wd-col>
|
|
|
|
|
|
</wd-row>
|
|
|
|
|
|
</view>
|
2025-12-19 11:14:02 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- MessageBox 组件 -->
|
|
|
|
|
|
<wd-message-box />
|
2025-12-18 16:49:44 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
.storage-cells-summary {
|
|
|
|
|
|
padding: 32rpx;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border-radius: 16rpx;
|
|
|
|
|
|
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.08);
|
|
|
|
|
|
|
|
|
|
|
|
.summary-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 32rpx;
|
|
|
|
|
|
|
|
|
|
|
|
.header-title {
|
|
|
|
|
|
font-size: 32rpx;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
2025-12-19 11:14:02 +08:00
|
|
|
|
|
|
|
|
|
|
.header-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 16rpx;
|
|
|
|
|
|
|
|
|
|
|
|
.back-btn {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 8rpx 12rpx;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border: 1px solid #e0e0e0;
|
|
|
|
|
|
border-radius: 16rpx;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
|
|
|
|
|
|
&:active {
|
|
|
|
|
|
background: #f5f5f5;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-18 16:49:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cell-type-selection {
|
|
|
|
|
|
margin-bottom: 48rpx;
|
|
|
|
|
|
|
2025-12-20 15:17:15 +08:00
|
|
|
|
.cell-type-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
|
gap: 16rpx;
|
|
|
|
|
|
|
|
|
|
|
|
.cell-type-card {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
padding: 32rpx 24rpx;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border: 1px solid #e0e0e0;
|
|
|
|
|
|
border-radius: 16rpx;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&:active:not(.cell-type-card--disabled) {
|
|
|
|
|
|
transform: translateY(2px);
|
|
|
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&--selected {
|
|
|
|
|
|
border-color: #2979ff;
|
|
|
|
|
|
background: rgba(41, 121, 255, 0.04);
|
|
|
|
|
|
box-shadow: 0 4rpx 16rpx rgba(41, 121, 255, 0.15);
|
|
|
|
|
|
|
|
|
|
|
|
.cell-type-name {
|
|
|
|
|
|
color: #2979ff;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cell-type-count {
|
|
|
|
|
|
color: #2979ff;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&--disabled {
|
|
|
|
|
|
background: #f9f9f9;
|
|
|
|
|
|
border-color: #f0f0f0;
|
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
|
|
|
|
|
|
|
.cell-type-name,
|
|
|
|
|
|
.cell-type-count {
|
|
|
|
|
|
color: #ccc;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cell-type-icon {
|
|
|
|
|
|
margin-bottom: 16rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cell-type-name {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
margin-bottom: 8rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cell-type-count {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cell-type-selected-indicator {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: -8rpx;
|
|
|
|
|
|
right: -8rpx;
|
|
|
|
|
|
width: 32rpx;
|
|
|
|
|
|
height: 32rpx;
|
|
|
|
|
|
background: #2979ff;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
box-shadow: 0 2rpx 8rpx rgba(41, 121, 255, 0.3);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-18 16:49:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.action-buttons {
|
|
|
|
|
|
margin-top: 32rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 状态样式
|
|
|
|
|
|
.loading-state,
|
|
|
|
|
|
.error-state,
|
|
|
|
|
|
.empty-state {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
padding: 64rpx 0;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
|
|
|
|
|
|
.loading-text,
|
|
|
|
|
|
.error-text,
|
|
|
|
|
|
.empty-text {
|
|
|
|
|
|
margin-top: 24rpx;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.error-state {
|
|
|
|
|
|
.error-text {
|
|
|
|
|
|
color: #F56C6C;
|
|
|
|
|
|
margin-bottom: 24rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-19 11:14:02 +08:00
|
|
|
|
|
|
|
|
|
|
.custom-message-box {
|
|
|
|
|
|
/* 保留空白,自动换行 */
|
|
|
|
|
|
white-space: pre-wrap;
|
|
|
|
|
|
}
|
2025-12-20 15:17:15 +08:00
|
|
|
|
|
|
|
|
|
|
.product-image {
|
|
|
|
|
|
width: 80rpx;
|
|
|
|
|
|
height: 80rpx;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
}
|
2025-12-18 16:49:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|