You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1046 lines
27 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="page-container">
<NavBar :title="t('sparepartInbound.createTitle')" />
<!-- 操作按钮区 -->
<view class="action-row">
<view class="action-btn scan-btn" @click="handleScan">
<view class="btn-icon-wrap">
<text class="iconfont icon-scan btn-icon"></text>
</view>
<text class="btn-text">{{ t('sparepartInbound.scanSparepart') }}</text>
</view>
<view class="action-btn select-btn" @click="handleSelectSparepart">
<view class="btn-icon-wrap">
<text class="iconfont icon-device btn-icon"></text>
</view>
<text class="btn-text">{{ t('sparepartInbound.selectSparepart') }}</text>
</view>
</view>
<!-- 已选备件 -->
<view v-if="selectedSparepart.id" class="sparepart-section">
<view class="section-title-bar">
<view class="section-bar-line"></view>
<text class="section-title">{{ t('sparepartInbound.selectedSpareparts') }}</text>
</view>
<view class="sparepart-card">
<!-- 上半部分:左侧图片 + 右侧基本信息 -->
<view class="card-top">
<!-- 左侧图片区 -->
<view class="card-image-wrap">
<image
v-if="sparepartImage"
:src="sparepartImage"
class="card-image"
mode="aspectFill"
/>
<view v-else class="card-image-empty">
<text class="empty-img-icon">📦</text>
</view>
</view>
<!-- 右侧基本信息 -->
<view class="card-info-right">
<view class="info-item">
<text class="info-label">备件名称:</text>
<text class="info-name">{{ textValue(selectedSparepart.name) }}</text>
</view>
<view class="info-item">
<text class="info-label">规格:</text>
<text class="info-value">{{ textValue(selectedSparepart.standard || selectedSparepart.deviceSpec) }}</text>
</view>
<view class="info-item">
<text class="info-label">当前库存:</text>
<text class="info-value stock-highlight">{{ textValue(selectedSparepart.stock) }}{{ textUnit(selectedSparepart.minStockUnitName) }}</text>
</view>
</view>
</view>
<!-- 下半部分:详细信息 -->
<view class="card-bottom">
<view class="detail-row two-col">
<view class="detail-col">
<text class="detail-label">采购单位:</text>
<text class="detail-value">{{ textValue(selectedSparepart.purchaseUnitName) }}</text>
</view>
<view class="detail-col">
<text class="detail-label">库存单位:</text>
<text class="detail-value">{{ textValue(selectedSparepart.unitName || selectedSparepart.minStockUnitName) }}</text>
</view>
</view>
<view class="detail-row">
<text class="detail-label">换算关系:</text>
<text class="detail-value">1{{ textValue(selectedSparepart.purchaseUnitName) }}={{ textValue(selectedSparepart.purchaseUnitConvertQuantity) }}{{ stockUnitLabel(selectedSparepart) }}</text>
</view>
<view class="detail-row">
<text class="detail-label">默认仓库</text>
<text class="detail-value" style="margin-left: 8rpx;">{{ selectedSparepart.warehouseName ? textValue(selectedSparepart.warehouseName) : '库区/备件仓' }}</text>
</view>
</view>
</view>
<!-- 入库数量 -->
<view class="inbound-qty-section">
<view class="section-title-bar">
<view class="section-bar-line"></view>
<text class="section-title">入库数量</text>
</view>
<view class="qty-input-card">
<view class="form-field">
<text class="form-label">入库数量</text>
<input
v-model="inboundQty"
class="form-input"
placeholder="请输入"
confirm-type="done"
/>
<text class="form-suffix-text">单位:{{ textValue(selectedSparepart.purchaseUnitName) }}</text>
</view>
<!-- 折算后库存数量 -->
<view class="convert-row">
<text class="convert-label-inline">折算后库存数量:</text>
<text class="convert-value-inline">{{ calculatedStock }}</text>
<text class="convert-unit-inline">{{ stockUnitLabel(selectedSparepart) }}</text>
</view>
<view class="convert-formula-inline">
{{ inboundQty || 0 }}{{ textValue(selectedSparepart.purchaseUnitName) }} × {{ textValue(selectedSparepart.purchaseUnitConvertQuantity) }}{{ stockUnitLabel(selectedSparepart) }} = {{ calculatedStock }}{{ stockUnitLabel(selectedSparepart) }}
</view>
</view>
<!-- 供应商(取备件默认供应商) -->
<view class="section-title-bar" style="padding-top: 24rpx;">
<view class="section-bar-line"></view>
<text class="section-title">供应商</text>
</view>
<view class="select-row-card">
<view class="full-dropdown">
<text :class="{ placeholder: !defaultSupplierName }">
{{ defaultSupplierName || '未配置默认供应商' }}
</text>
</view>
</view>
<!-- 经办人 -->
<view class="section-title-bar" style="padding-top: 24rpx;">
<view class="section-bar-line"></view>
<text class="section-title">经办人</text>
</view>
<view class="select-row-card" @click="toggleOperatorDropdown">
<view class="full-dropdown">
<text :class="{ placeholder: !selectedOperator }">
{{ selectedOperator ? selectedOperator.label : '请选择经办人' }}
</text>
<text class="dropdown-arrow">▼</text>
</view>
<view v-if="showOperatorDropdown" class="dropdown-panel">
<scroll-view scroll-y class="dropdown-scroll">
<view
v-for="item in operatorOptions"
:key="item.value"
class="dropdown-item"
:class="{ active: selectedOperator?.value === item.value }"
@click.stop="handleSelectOperator(item)"
>
<text class="dropdown-item-text">{{ item.label }}</text>
<text v-if="selectedOperator?.value === item.value" class="dropdown-check">✓</text>
</view>
<view v-if="!operatorOptions.length" class="dropdown-empty">暂无数据</view>
</scroll-view>
</view>
</view>
<!-- 仓库 / 库区 -->
<view class="section-title-bar" style="padding-top: 24rpx;">
<view class="section-bar-line"></view>
<text class="section-title">仓库/库区</text>
</view>
<view class="select-row-card warehouse-area-card">
<view class="warehouse-area-rows">
<!-- 仓库行 -->
<view class="warehouse-area-row">
<text class="warehouse-area-label">仓库</text>
<view class="warehouse-area-dropdown" @click="toggleWarehouseDropdown">
<view class="dropdown-input">
<text :class="['dropdown-value', { placeholder: !selectedWarehouse }]">
{{ selectedWarehouse ? selectedWarehouse.label : '请选择' }}
</text>
<text class="dropdown-arrow">▼</text>
</view>
<view v-if="showWarehouseDropdown" class="dropdown-panel">
<scroll-view scroll-y class="dropdown-scroll">
<view
v-for="item in warehouseOptions"
:key="item.value"
class="dropdown-item"
:class="{ active: selectedWarehouse?.value === item.value }"
@click.stop="handleSelectWarehouse(item)"
>
<text class="dropdown-item-text">{{ item.label }}</text>
<text v-if="selectedWarehouse?.value === item.value" class="dropdown-check">✓</text>
</view>
<view v-if="!warehouseOptions.length" class="dropdown-empty">暂无数据</view>
</scroll-view>
</view>
</view>
</view>
<!-- 库区行 -->
<view class="warehouse-area-row">
<text class="warehouse-area-label">库区</text>
<view class="warehouse-area-dropdown" @click="toggleAreaDropdown">
<view class="dropdown-input">
<text :class="['dropdown-value', { placeholder: !selectedArea }]">
{{ selectedArea ? selectedArea.label : (loadingAreas ? '加载中...' : '请选择') }}
</text>
<text class="dropdown-arrow">▼</text>
</view>
<view v-if="showAreaDropdown" class="dropdown-panel">
<scroll-view scroll-y class="dropdown-scroll">
<view
v-for="item in areaOptions"
:key="item.value"
class="dropdown-item"
:class="{ active: selectedArea?.value === item.value }"
@click.stop="handleSelectArea(item)"
>
<text class="dropdown-item-text">{{ item.label }}</text>
<text v-if="selectedArea?.value === item.value" class="dropdown-check">✓</text>
</view>
<view v-if="!areaOptions.length" class="dropdown-empty">暂无数据</view>
</scroll-view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-else class="empty-wrap">
<text class="empty-text">{{ t('sparepartInbound.noSelectedSparepart') }}</text>
</view>
<!-- 底部操作栏 -->
<view class="bottom-actions">
<view class="bottom-btn cancel-btn" @click="handleCancel">取消</view>
<view class="bottom-btn confirm-btn" @click="handleSubmit"></view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import { onShow, onHide } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import NavBar from '@/components/common/NavBar.vue'
import { getSimpleUserList, getWarehouseSimpleList, getWarehouseAreaSimpleList } from '@/api/mes/moldget'
import { createSparepartInbound } from '@/api/mes/sparepartInbound'
import { getSparepartDetail } from '@/api/mes/sparepart'
const { t } = useI18n()
const selectedSparepart = ref({})
const inboundQty = ref(null)
// 备件默认供应商(从备件数据中取)
const defaultSupplierName = computed(() => {
const suppliers = selectedSparepart.value.suppliers
const defaultId = selectedSparepart.value.defaultSupplierId
if (!suppliers || !suppliers.length) return ''
if (defaultId) {
const found = suppliers.find(s => s.supplierId === defaultId || s.id === defaultId)
if (found) return found.supplierName || ''
}
// 没配默认供应商,取第一个标记为默认的
const firstDefault = suppliers.find(s => s.defaultStatus === 1)
return firstDefault?.supplierName || ''
})
const defaultSupplierId = computed(() => {
const suppliers = selectedSparepart.value.suppliers
const defaultId = selectedSparepart.value.defaultSupplierId
if (!suppliers || !suppliers.length) return undefined
if (defaultId) {
const found = suppliers.find(s => s.supplierId === defaultId || s.id === defaultId)
if (found) return found.supplierId || found.id
}
const firstDefault = suppliers.find(s => s.defaultStatus === 1)
return firstDefault?.supplierId || firstDefault?.id
})
// 经办人下拉
const operatorOptions = ref([])
const selectedOperator = ref(null)
const showOperatorDropdown = ref(false)
// 仓库下拉
const warehouseOptions = ref([])
const selectedWarehouse = ref(null)
const showWarehouseDropdown = ref(false)
// 库区下拉
const areaOptions = ref([])
const selectedArea = ref(null)
const showAreaDropdown = ref(false)
const loadingAreas = ref(false)
// 备件图片(后端返回 images 字段,逗号分隔多图 URL取第一张
const sparepartImage = computed(() => {
const images = selectedSparepart.value.images
if (!images) return ''
if (Array.isArray(images)) return String(images[0] || '')
return String(images).split(',')[0]?.trim() || ''
})
// 折算后库存数量
const calculatedStock = computed(() => {
const qty = Number(inboundQty.value) || 0
const ratio = Number(selectedSparepart.value.purchaseUnitConvertQuantity) || 0
return qty * ratio
})
function textValue(v) {
if (v === 0) return '0'
if (v == null) return '-'
const s = String(v).trim()
return s || '-'
}
function textUnit(v) {
if (v === 0) return '0'
if (v == null) return ''
return String(v).trim()
}
function stockUnitLabel(item) {
return item.unitName || item.minStockUnitName || '个'
}
function handleScan() {
uni.scanCode({
onlyFromCamera: true,
scanType: ['barCode', 'qrCode'],
success: (res) => {
console.log('扫码结果:', res.result)
// TODO: 根据扫码结果查询备件信息并添加到列表
uni.showToast({ title: '扫码成功: ' + res.result, icon: 'none' })
},
fail: () => {
uni.showToast({ title: t('functionCommon.scanFailed'), icon: 'none' })
}
})
}
function handleCancel() {
uni.navigateBack()
}
function handleSelectSparepart() {
uni.navigateTo({
url: '/pages_function/pages/sparepartInbound/sparepartSelect'
})
}
function toggleOperatorDropdown() {
showOperatorDropdown.value = !showOperatorDropdown.value
}
function handleSelectOperator(item) {
selectedOperator.value = item
showOperatorDropdown.value = false
}
async function loadOperators() {
try {
const res = await getSimpleUserList()
const data = Array.isArray(res) ? res : (Array.isArray(res?.data) ? res.data : [])
operatorOptions.value = data.map((u) => ({
value: u.id || u.userId,
label: u.nickname || u.userName || u.name || String(u.id || '')
}))
} catch (e) {
console.error('loadOperators error', e)
}
}
function toggleWarehouseDropdown() {
showWarehouseDropdown.value = !showWarehouseDropdown.value
}
function handleSelectWarehouse(item) {
selectedWarehouse.value = item
showWarehouseDropdown.value = false
// 清空之前选择的库区
selectedArea.value = null
areaOptions.value = []
// 自动加载该仓库的库区列表
loadAreas(item.value)
}
function toggleAreaDropdown() {
if (!selectedWarehouse.value) {
uni.showToast({ title: '请先选择仓库', icon: 'none' })
return
}
showAreaDropdown.value = !showAreaDropdown.value
}
function handleSelectArea(item) {
selectedArea.value = item
showAreaDropdown.value = false
}
async function loadAreas(warehouseId) {
if (!warehouseId) return
loadingAreas.value = true
areaOptions.value = []
try {
const res = await getWarehouseAreaSimpleList(warehouseId)
const data = Array.isArray(res) ? res : (Array.isArray(res?.data) ? res.data : [])
areaOptions.value = data.map((a) => ({
value: a.id,
label: a.name || a.areaName || String(a.id || '')
}))
} catch (e) {
console.error('loadAreas error', e)
} finally {
loadingAreas.value = false
}
}
async function loadWarehouses() {
try {
const res = await getWarehouseSimpleList()
const data = Array.isArray(res) ? res : (Array.isArray(res?.data) ? res.data : [])
warehouseOptions.value = data.map((w) => ({
value: w.id,
label: w.name || String(w.id || '')
}))
} catch (e) {
console.error('loadWarehouses error', e)
}
}
async function handleSubmit() {
if (!selectedSparepart.value.id) {
uni.showToast({ title: '请先选择备件', icon: 'none' })
return
}
if (!inboundQty.value || Number(inboundQty.value) <= 0) {
uni.showToast({ title: '请输入入库数量', icon: 'none' })
return
}
if (!selectedOperator.value) {
uni.showToast({ title: '请选择经办人', icon: 'none' })
return
}
if (!selectedWarehouse.value) {
uni.showToast({ title: '请选择仓库', icon: 'none' })
return
}
if (!selectedArea.value) {
uni.showToast({ title: '请选择库区', icon: 'none' })
return
}
const inputCount = Number(inboundQty.value)
const convertRatio = Number(selectedSparepart.value.purchaseUnitConvertQuantity) || 1
const count = inputCount * convertRatio
const submitData = {
isCode: true,
inTime: Date.now(),
stockUserId: String(selectedOperator.value.value),
supplierId: defaultSupplierId.value,
status: 0,
totalCount: count,
totalPrice: 0,
items: [{
warehouseId: selectedWarehouse.value.value,
areaId: selectedArea.value.value,
productId: selectedSparepart.value.id,
productName: selectedSparepart.value.name || '',
productBarCode: selectedSparepart.value.barCode || '',
productUnitName: selectedSparepart.value.unitName || selectedSparepart.value.minStockUnitName || '',
purchaseUnitName: selectedSparepart.value.purchaseUnitName || '',
purchaseUnitConvertQuantity: convertRatio,
inputCount: inputCount,
count: count
}]
}
console.log('提交数据:', JSON.stringify(submitData))
uni.showLoading({ title: '提交中...', mask: true })
try {
await createSparepartInbound(submitData)
uni.hideLoading()
uni.showToast({ title: '入库成功', icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (e) {
uni.hideLoading()
const msg = e?.message || e?.data?.msg || e?.response?.data?.msg || t('functionCommon.saveFailed')
console.error('入库提交失败:', e)
uni.showToast({ title: String(msg).substring(0, 50), icon: 'none' })
}
}
onShow(async () => {
const selectResult = getApp().globalData?._sparepartSelectResult
if (selectResult) {
selectedSparepart.value = selectResult
console.log('[sparepartInbound] 已选备件 suppliers:', JSON.stringify(selectResult.suppliers))
console.log('[sparepartInbound] 已选备件 defaultSupplierId:', selectResult.defaultSupplierId)
// 补充获取备件详情(含供应商、图片等)
if (selectResult.id) {
try {
const res = await getSparepartDetail(selectResult.id)
const detail = res?.data || res
if (detail && detail.suppliers) {
console.log('[sparepartInbound] 详情接口 suppliers:', JSON.stringify(detail.suppliers))
selectedSparepart.value = { ...selectedSparepart.value, ...detail }
}
} catch (e) {
console.error('获取备件详情失败:', e)
}
}
inboundQty.value = null
getApp().globalData._sparepartSelectResult = null
}
loadOperators()
loadWarehouses()
})
onHide(() => {
showOperatorDropdown.value = false
showWarehouseDropdown.value = false
showAreaDropdown.value = false
})
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background: #f5f6f8;
padding-bottom: calc(120rpx + env(safe-area-inset-bottom));
}
/* ====== 操作按钮区 ====== */
.action-row {
display: flex;
gap: 20rpx;
padding: 20rpx 24rpx;
}
.action-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
height: 96rpx;
border-radius: 12rpx;
background: #1f4b79;
color: #fff;
font-size: 28rpx;
font-weight: 600;
.btn-icon-wrap {
width: 44rpx;
height: 44rpx;
display: flex;
align-items: center;
justify-content: center;
}
.btn-icon {
font-size: 36rpx;
color: #fff;
}
.btn-text {
font-size: 28rpx;
font-weight: 600;
color: #fff;
}
}
/* ====== 已选备件区域 ====== */
.sparepart-section {
padding: 0;
}
.section-title-bar {
display: flex;
align-items: center;
gap: 10rpx;
padding: 24rpx 24rpx 18rpx;
}
.section-bar-line {
width: 6rpx;
height: 32rpx;
border-radius: 3rpx;
background: #2563eb;
flex-shrink: 0;
}
.section-title {
font-size: 30rpx;
font-weight: 700;
color: #1a1a1a;
}
.sparepart-card {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin: 0 24rpx 20rpx;
box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.04);
}
/* ====== 卡片上半部分:图片 + 信息 + 删除 ====== */
.card-top {
display: flex;
position: relative;
}
.card-image-wrap {
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
overflow: hidden;
background: #f8fafc;
border: 1rpx solid #f0f0f0;
flex-shrink: 0;
margin-right: 20rpx;
}
.card-image {
width: 100%;
height: 100%;
}
.card-image-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: #f8fafc;
}
.empty-img-icon {
font-size: 56rpx;
opacity: 0.3;
}
/* 右侧信息区 */
.card-info-right {
flex: 1;
min-width: 0;
}
.info-item {
display: flex;
align-items: center;
margin-bottom: 8rpx;
}
.info-label {
font-size: 26rpx;
color: #6b7280;
flex-shrink: 0;
}
.info-name {
font-size: 30rpx;
font-weight: 700;
color: #0f172a;
}
.info-value {
font-size: 26rpx;
color: #374151;
&.stock-highlight {
color: #2563eb;
font-weight: 500;
}
}
/* 删除按钮 */
.card-delete {
position: absolute;
top: -8rpx;
right: -8rpx;
width: 48rpx;
height: 48rpx;
border-radius: 24rpx;
background: #fee2e2;
display: flex;
align-items: center;
justify-content: center;
}
.delete-icon {
font-size: 22rpx;
color: #dc2626;
}
/* ====== 卡片下半部分:详细信息 ====== */
.card-bottom {
margin-top: 20rpx;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
}
.detail-row {
display: flex;
align-items: center;
margin-bottom: 14rpx;
&:last-child {
margin-bottom: 0;
}
&.two-col {
justify-content: space-between;
}
}
.detail-col {
display: flex;
align-items: center;
flex: 1;
}
.detail-label {
font-size: 24rpx;
color: #9ca3af;
flex-shrink: 0;
}
.detail-value {
font-size: 24rpx;
color: #4b5563;
}
/* ====== 入库数量 ====== */
.inbound-qty-section {
margin-top: 4rpx;
}
.qty-input-card {
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin: 0 24rpx;
box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.04);
}
.form-field {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.form-label {
font-size: 26rpx;
color: #4b5563;
font-weight: 500;
}
.form-input {
width: 100%;
height: 88rpx;
padding: 0 24rpx;
font-size: 28rpx;
color: #374151;
background: #f8fafc;
border-radius: 14rpx;
box-sizing: border-box;
}
.form-suffix-text {
display: block;
margin-top: 10rpx;
font-size: 24rpx;
color: #9ca3af;
}
/* ====== 折算后库存数量(内嵌在入库数量卡片) ====== */
.convert-row {
margin-top: 20rpx;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
display: flex;
align-items: center;
}
.convert-label-inline {
font-size: 26rpx;
color: #6b7280;
}
.convert-value-inline {
font-size: 36rpx;
font-weight: 700;
color: #1f2937;
margin-left: auto;
}
.convert-unit-inline {
font-size: 24rpx;
color: #64748b;
margin-left: 8rpx;
}
.convert-formula-inline {
margin-top: 10rpx;
font-size: 22rpx;
color: #9ca3af;
}
/* ====== 供应商/经办人 选择行 ====== */
.select-row-card {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
background: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin: 0 24rpx 20rpx;
box-shadow: 0 4rpx 16rpx rgba(15, 23, 42, 0.04);
.dropdown-panel {
position: absolute;
top: 100%;
left: 0;
right: 0;
z-index: 99;
margin-top: 4rpx;
background: #fff;
border: 1rpx solid #e0e0e0;
border-radius: 12rpx;
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.1);
overflow: hidden;
}
}
.select-row-left {
display: flex;
align-items: center;
flex-shrink: 0;
}
.select-row-icon {
font-size: 36rpx;
color: #3b82f6;
margin-right: 16rpx;
flex-shrink: 0;
}
.select-row-label {
font-size: 28rpx;
color: #374151;
}
.full-dropdown {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
font-size: 28rpx;
color: #374151;
.placeholder {
color: #9ca3af;
}
}
.select-row-dropdown {
flex: 1;
min-width: 0;
margin-left: 24rpx;
}
/* ====== 仓库/库区 两行布局 ====== */
.warehouse-area-rows {
width: 100%;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.warehouse-area-row {
display: flex;
align-items: center;
}
.warehouse-area-label {
width: 80rpx;
font-size: 26rpx;
color: #6b7280;
flex-shrink: 0;
}
.warehouse-area-dropdown {
flex: 1;
min-width: 0;
position: relative;
.dropdown-panel {
position: absolute;
top: 100%;
left: 0;
right: 0;
z-index: 100;
margin-top: 4rpx;
background: #fff;
border: 1rpx solid #e0e0e0;
border-radius: 12rpx;
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.1);
overflow: hidden;
}
}
.warehouse-area-card {
align-items: flex-start;
flex-wrap: wrap;
}
/* ====== 下拉选择器 ====== */
.select-dropdown {
position: relative;
width: 100%;
}
.dropdown-input {
display: flex;
align-items: center;
height: 64rpx;
padding: 0 20rpx;
border: 1rpx solid #e0e0e0;
border-radius: 10rpx;
background: #f9fafb;
}
.dropdown-value {
flex: 1;
font-size: 27rpx;
color: #333;
&.placeholder {
color: #bbb;
}
}
.dropdown-arrow {
font-size: 20rpx;
color: #999;
flex-shrink: 0;
}
.dropdown-panel {
position: absolute;
top: 68rpx;
left: 0;
right: 0;
z-index: 99;
background: #fff;
border: 1rpx solid #e0e0e0;
border-radius: 12rpx;
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.dropdown-scroll {
max-height: 360rpx;
}
.dropdown-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 24rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: 0;
}
&.active {
background: #f0f5ff;
}
}
.dropdown-item-text {
font-size: 27rpx;
color: #333;
}
.dropdown-check {
font-size: 28rpx;
color: #2563eb;
font-weight: 700;
}
.dropdown-empty {
padding: 32rpx;
text-align: center;
color: #999;
font-size: 26rpx;
}
/* ====== 空状态 ====== */
.empty-wrap {
padding: 160rpx 24rpx;
text-align: center;
}
.empty-text {
font-size: 28rpx;
color: #94a3b8;
}
/* ====== 底部操作栏 ====== */
.bottom-actions {
position: fixed;
left: 0;
right: 0;
bottom: 0;
display: flex;
gap: 18rpx;
padding: 18rpx 24rpx calc(18rpx + env(safe-area-inset-bottom));
background: #ffffff;
box-shadow: 0 -8rpx 24rpx rgba(15, 23, 42, 0.06);
z-index: 99;
}
.bottom-btn {
flex: 1;
height: 84rpx;
line-height: 84rpx;
text-align: center;
border-radius: 16rpx;
font-size: 30rpx;
font-weight: 600;
&:active {
opacity: 0.85;
}
}
.cancel-btn {
background: #eef2f7;
color: #475569;
}
.confirm-btn {
background: #1f4b79;
color: #ffffff;
}
</style>