|
|
@@ -28,7 +28,7 @@
|
|
|
<div v-if="isView" class="form-input">
|
|
|
{{ formData.partnerCompany || '--' }}
|
|
|
</div>
|
|
|
- <div v-else class="form-input" @click="showCompanyPicker = true">
|
|
|
+ <div v-else class="form-input" @click="showCustomerPicker = true">
|
|
|
<span :class="{ placeholder: !formData.partnerCompany }">
|
|
|
{{ formData.partnerCompany || '请选择往来单位' }}
|
|
|
</span>
|
|
|
@@ -49,11 +49,11 @@
|
|
|
<div class="form-item">
|
|
|
<div class="form-label">产品 <span class="required">*</span></div>
|
|
|
<div v-if="isView" class="form-input">
|
|
|
- {{ formData.product || '--' }}
|
|
|
+ {{ productDisplay || '--' }}
|
|
|
</div>
|
|
|
- <div v-else class="form-input" @click="showCargoPicker = true">
|
|
|
- <span :class="{ placeholder: !formData.product }">
|
|
|
- {{ formData.product || '请选择产品' }}
|
|
|
+ <div v-else class="form-input" @click="showProductPicker = true">
|
|
|
+ <span :class="{ placeholder: !productDisplay }">
|
|
|
+ {{ productDisplay || '请选择产品' }}
|
|
|
</span>
|
|
|
<van-icon name="arrow" class="arrow-icon" />
|
|
|
</div>
|
|
|
@@ -79,7 +79,7 @@
|
|
|
</div>
|
|
|
<div v-else class="form-input" @click="showVehiclePicker = true">
|
|
|
<span :class="{ placeholder: !formData.vehicleNo }">
|
|
|
- {{ formData.vehicleNo || '请选择或输入车牌号' }}
|
|
|
+ {{ formData.vehicleNo || '请选择车牌号' }}
|
|
|
</span>
|
|
|
<van-icon name="arrow" class="arrow-icon" />
|
|
|
</div>
|
|
|
@@ -125,175 +125,55 @@
|
|
|
</van-form>
|
|
|
</div>
|
|
|
|
|
|
- <!-- 公司选择器 -->
|
|
|
- <van-popup v-model:show="showCompanyPicker" round position="bottom" :style="{ maxHeight: '70vh' }">
|
|
|
- <div class="company-picker-container">
|
|
|
- <div class="search-container">
|
|
|
- <van-search
|
|
|
- v-model="companySearchKeyword"
|
|
|
- placeholder="搜索往来单位名称"
|
|
|
- @search="handleCompanySearch"
|
|
|
- @clear="handleCompanySearch"
|
|
|
- shape="round"
|
|
|
- />
|
|
|
- </div>
|
|
|
- <van-picker
|
|
|
- :columns="filteredCompanyOptions"
|
|
|
- show-toolbar
|
|
|
- @confirm="onCompanyConfirm"
|
|
|
- @cancel="showCompanyPicker = false"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </van-popup>
|
|
|
-
|
|
|
- <!-- 货物选择器 -->
|
|
|
- <van-popup v-model:show="showCargoPicker" round position="bottom" :style="{ maxHeight: '70vh' }">
|
|
|
- <div class="company-picker-container">
|
|
|
- <div class="search-container">
|
|
|
- <van-search
|
|
|
- v-model="cargoSearchKeyword"
|
|
|
- placeholder="搜索产品名称"
|
|
|
- @search="handleCargoSearch"
|
|
|
- @clear="handleCargoSearch"
|
|
|
- shape="round"
|
|
|
- />
|
|
|
- </div>
|
|
|
- <van-picker
|
|
|
- :columns="filteredCargoOptions"
|
|
|
- show-toolbar
|
|
|
- @confirm="onCargoConfirm"
|
|
|
- @cancel="showCargoPicker = false"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </van-popup>
|
|
|
+ <DbCustomerPicker v-model:show="showCustomerPicker" @select="onCustomerSelect" />
|
|
|
+ <DbProductPicker v-model:show="showProductPicker" @select="onProductSelect" />
|
|
|
+ <DbVehiclePicker v-model:show="showVehiclePicker" @select="onVehicleSelect" />
|
|
|
|
|
|
- <!-- 日期选择器 -->
|
|
|
<van-calendar
|
|
|
v-model:show="showDateCalendar"
|
|
|
v-model="dateSelected"
|
|
|
@confirm="onDateConfirm"
|
|
|
/>
|
|
|
-
|
|
|
- <!-- 车牌号选择器:支持搜索 + 手动输入 -->
|
|
|
- <van-popup v-model:show="showVehiclePicker" round position="bottom" :style="{ maxHeight: '70vh' }">
|
|
|
- <div class="vehicle-picker-container">
|
|
|
- <div class="search-container">
|
|
|
- <van-search
|
|
|
- v-model="vehicleSearchKeyword"
|
|
|
- placeholder="搜索车牌号"
|
|
|
- @search="handleVehicleSearch"
|
|
|
- @clear="handleVehicleSearch"
|
|
|
- @update:model-value="handleVehicleSearch"
|
|
|
- shape="round"
|
|
|
- />
|
|
|
- </div>
|
|
|
- <div class="vehicle-list">
|
|
|
- <van-cell
|
|
|
- v-for="item in filteredVehicleOptions"
|
|
|
- :key="item.vehicleNo"
|
|
|
- :title="item.vehicleNo"
|
|
|
- clickable
|
|
|
- @click="onVehicleSelect(item.vehicleNo)"
|
|
|
- />
|
|
|
- <van-cell v-if="filteredVehicleOptions.length === 0 && !vehicleLoading" title="暂无匹配车牌号,可点击下方手动输入" />
|
|
|
- </div>
|
|
|
- <div class="manual-input-row">
|
|
|
- <van-button block plain type="primary" @click="showManualVehicleInput = true">
|
|
|
- 手动输入新车牌号
|
|
|
- </van-button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </van-popup>
|
|
|
-
|
|
|
- <!-- 手动输入车牌号弹层 -->
|
|
|
- <van-dialog
|
|
|
- v-model:show="showManualVehicleInput"
|
|
|
- title="输入车牌号"
|
|
|
- show-cancel-button
|
|
|
- @confirm="onManualVehicleConfirm"
|
|
|
- >
|
|
|
- <van-field
|
|
|
- v-model="manualVehicleNo"
|
|
|
- placeholder="请输入车牌号"
|
|
|
- clearable
|
|
|
- class="dialog-field"
|
|
|
- />
|
|
|
- </van-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, reactive, computed, onMounted, watch } from 'vue'
|
|
|
+import { ref, reactive, computed, onMounted } from 'vue'
|
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
|
import { showToast } from 'vant'
|
|
|
import { inventoryApi, type InventoryRecord, type InventoryCreate } from '@/api/inventoryApi'
|
|
|
-import { companyApi, cargoApi, vehicleApi } from '@/api/masterDataApi'
|
|
|
+import type { CrmCustomerOption, CrmProductOption } from '@/api/crmOptionsApi'
|
|
|
+import DbCustomerPicker from '@/components/pickers/DbCustomerPicker.vue'
|
|
|
+import DbProductPicker from '@/components/pickers/DbProductPicker.vue'
|
|
|
+import DbVehiclePicker from '@/components/pickers/DbVehiclePicker.vue'
|
|
|
import dayjs from 'dayjs'
|
|
|
|
|
|
const router = useRouter()
|
|
|
const route = useRoute()
|
|
|
|
|
|
const isView = computed(() => route.query.mode === 'view')
|
|
|
-const isEdit = computed(() => route.query.mode === 'edit' || route.params.id)
|
|
|
-// 计划带入新增时计划号锁定,避免误改
|
|
|
+const isEdit = computed(() => route.query.mode === 'edit' || !!route.params.id)
|
|
|
const isPlanNoReadonly = computed(() => !!route.query.planNo && !isEdit.value)
|
|
|
-// const isAdd = computed(() => !isView.value && !isEdit.value) // 未使用
|
|
|
|
|
|
const saving = ref(false)
|
|
|
const editingRecord = ref<InventoryRecord | null>(null)
|
|
|
-const showCompanyPicker = ref(false)
|
|
|
-const showCargoPicker = ref(false)
|
|
|
+const showCustomerPicker = ref(false)
|
|
|
+const showProductPicker = ref(false)
|
|
|
const showVehiclePicker = ref(false)
|
|
|
-const showManualVehicleInput = ref(false)
|
|
|
const showDateCalendar = ref(false)
|
|
|
const dateSelected = ref<Date | null>(null)
|
|
|
-const companyOptions = ref<{ text: string; value: string }[]>([])
|
|
|
-const vehicleOptions = ref<{ vehicleNo: string }[]>([])
|
|
|
-const vehicleSearchKeyword = ref('')
|
|
|
-const vehicleLoading = ref(false)
|
|
|
-const manualVehicleNo = ref('')
|
|
|
-
|
|
|
-// 公司搜索相关
|
|
|
-const companySearchKeyword = ref('')
|
|
|
-const allCompanyOptions = ref<{ text: string; value: string }[]>([])
|
|
|
-const filteredCompanyOptions = computed(() => {
|
|
|
- if (!companySearchKeyword.value) {
|
|
|
- return [allCompanyOptions.value]
|
|
|
- }
|
|
|
- const filtered = allCompanyOptions.value.filter(item =>
|
|
|
- item.text.toLowerCase().includes(companySearchKeyword.value.toLowerCase())
|
|
|
- )
|
|
|
- return [filtered]
|
|
|
-})
|
|
|
-
|
|
|
-// 产品搜索相关
|
|
|
-const cargoSearchKeyword = ref('')
|
|
|
-const allCargoOptions = ref<{ text: string; value: string }[]>([])
|
|
|
-const filteredCargoOptions = computed(() => {
|
|
|
- if (!cargoSearchKeyword.value) return [allCargoOptions.value]
|
|
|
- const filtered = allCargoOptions.value.filter(item =>
|
|
|
- item.text.toLowerCase().includes(cargoSearchKeyword.value.toLowerCase())
|
|
|
- )
|
|
|
- return [filtered]
|
|
|
-})
|
|
|
-
|
|
|
-const filteredVehicleOptions = computed(() => {
|
|
|
- if (!vehicleSearchKeyword.value) return vehicleOptions.value
|
|
|
- const kw = vehicleSearchKeyword.value.toLowerCase()
|
|
|
- return vehicleOptions.value.filter(item =>
|
|
|
- item.vehicleNo.toLowerCase().includes(kw)
|
|
|
- )
|
|
|
-})
|
|
|
+const productDisplay = ref((route.query.product as string) || '')
|
|
|
|
|
|
-// 如果是从计划管理跳转过来的(有planNo参数),日期为空;否则使用当前日期
|
|
|
const initialDate = route.query.planNo ? '' : dayjs().format('YYYY-MM-DD')
|
|
|
-const purchasePlanIdFromRoute = route.query.purchasePlanId ? Number(route.query.purchasePlanId) : undefined
|
|
|
+const purchasePlanIdFromRoute = route.query.purchasePlanId
|
|
|
+ ? String(route.query.purchasePlanId)
|
|
|
+ : undefined
|
|
|
|
|
|
const formData = reactive<InventoryCreate>({
|
|
|
date: initialDate,
|
|
|
partnerCompany: (route.query.customerName as string) || '',
|
|
|
businessType: (route.query.businessType as '入库' | '出库') || '入库',
|
|
|
- product: (route.query.product as string) || '',
|
|
|
+ productId: (route.query.productId as string) || '',
|
|
|
quantity: 32,
|
|
|
vehicleNo: '',
|
|
|
planNo: (route.query.planNo as string) || '',
|
|
|
@@ -301,91 +181,19 @@ const formData = reactive<InventoryCreate>({
|
|
|
purchasePlanId: purchasePlanIdFromRoute
|
|
|
})
|
|
|
|
|
|
-// 初始化日期选择器的默认值(如果日期为空,使用当前日期作为选择器的默认值)
|
|
|
dateSelected.value = route.query.planNo ? null : dayjs().toDate()
|
|
|
|
|
|
-const loadCompanies = async () => {
|
|
|
- try {
|
|
|
- const companies = await companyApi.list()
|
|
|
- allCompanyOptions.value = companies.map(item => ({
|
|
|
- text: item.name,
|
|
|
- value: item.name
|
|
|
- }))
|
|
|
- companyOptions.value = allCompanyOptions.value
|
|
|
- } catch (error) {
|
|
|
- console.error('加载公司列表失败', error)
|
|
|
- }
|
|
|
+const onCustomerSelect = (item: CrmCustomerOption) => {
|
|
|
+ formData.partnerCompany = item.customerName
|
|
|
}
|
|
|
|
|
|
-const handleCompanySearch = () => {
|
|
|
- // 触发计算属性更新
|
|
|
-}
|
|
|
-
|
|
|
-const loadCargos = async () => {
|
|
|
- try {
|
|
|
- const cargos = await cargoApi.list()
|
|
|
- allCargoOptions.value = cargos.map(item => ({
|
|
|
- text: item.name,
|
|
|
- value: item.name
|
|
|
- }))
|
|
|
- } catch (error) {
|
|
|
- console.error('加载货物列表失败', error)
|
|
|
- }
|
|
|
+const onProductSelect = (item: CrmProductOption) => {
|
|
|
+ formData.productId = item.id
|
|
|
+ productDisplay.value = item.productName
|
|
|
}
|
|
|
|
|
|
-const handleCargoSearch = () => {
|
|
|
- // 触发 computed 过滤
|
|
|
-}
|
|
|
-
|
|
|
-const loadVehicles = async () => {
|
|
|
- vehicleLoading.value = true
|
|
|
- try {
|
|
|
- const list = await vehicleApi.list(vehicleSearchKeyword.value || undefined)
|
|
|
- vehicleOptions.value = list.map(item => ({ vehicleNo: item.vehicleNo }))
|
|
|
- } catch (error) {
|
|
|
- console.error('加载车牌号列表失败', error)
|
|
|
- } finally {
|
|
|
- vehicleLoading.value = false
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-const handleVehicleSearch = () => {
|
|
|
- loadVehicles()
|
|
|
-}
|
|
|
-
|
|
|
-const onVehicleSelect = (vehicleNo: string) => {
|
|
|
- formData.vehicleNo = vehicleNo
|
|
|
- showVehiclePicker.value = false
|
|
|
-}
|
|
|
-
|
|
|
-const onManualVehicleConfirm = () => {
|
|
|
- const v = (manualVehicleNo.value || '').trim()
|
|
|
- if (v) {
|
|
|
- formData.vehicleNo = v
|
|
|
- showVehiclePicker.value = false
|
|
|
- }
|
|
|
- showManualVehicleInput.value = false
|
|
|
- manualVehicleNo.value = ''
|
|
|
-}
|
|
|
-
|
|
|
-// 保存前确保车牌号已写入车牌号表(新车牌则先创建)
|
|
|
-const ensureVehicleExists = async (vehicleNo: string) => {
|
|
|
- if (!vehicleNo?.trim()) return
|
|
|
- try {
|
|
|
- await vehicleApi.create({ vehicleNo: vehicleNo.trim(), remarks: null })
|
|
|
- } catch (err: any) {
|
|
|
- if (err.response?.status !== 409) throw err
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-const onCompanyConfirm = ({ selectedOptions }: any) => {
|
|
|
- formData.partnerCompany = selectedOptions[0].value
|
|
|
- showCompanyPicker.value = false
|
|
|
-}
|
|
|
-
|
|
|
-const onCargoConfirm = ({ selectedOptions }: any) => {
|
|
|
- formData.product = selectedOptions[0].value
|
|
|
- showCargoPicker.value = false
|
|
|
+const onVehicleSelect = (licensePlate: string) => {
|
|
|
+ formData.vehicleNo = licensePlate
|
|
|
}
|
|
|
|
|
|
const onDateConfirm = (value: Date | Date[]) => {
|
|
|
@@ -396,22 +204,22 @@ const onDateConfirm = (value: Date | Date[]) => {
|
|
|
}
|
|
|
|
|
|
const loadData = async () => {
|
|
|
- if (isEdit.value && route.params.id) {
|
|
|
+ if ((isEdit.value || isView.value) && route.params.id) {
|
|
|
try {
|
|
|
- const record = await inventoryApi.get(Number(route.params.id))
|
|
|
+ const record = await inventoryApi.get(String(route.params.id))
|
|
|
editingRecord.value = record
|
|
|
Object.assign(formData, {
|
|
|
date: record.date,
|
|
|
partnerCompany: record.partnerCompany,
|
|
|
businessType: record.businessType,
|
|
|
- product: record.product,
|
|
|
+ productId: record.productId || '',
|
|
|
quantity: record.quantity,
|
|
|
vehicleNo: record.vehicleNo || '',
|
|
|
planNo: record.planNo || '',
|
|
|
remarks: record.remarks || '',
|
|
|
purchasePlanId: record.purchasePlanId
|
|
|
})
|
|
|
- // 初始化日期选择器的值(用于编辑时回显)
|
|
|
+ productDisplay.value = record.product || ''
|
|
|
if (record.date) {
|
|
|
dateSelected.value = dayjs(record.date).toDate()
|
|
|
}
|
|
|
@@ -423,16 +231,19 @@ const loadData = async () => {
|
|
|
}
|
|
|
|
|
|
const handleSubmit = async () => {
|
|
|
- if (!formData.date || !formData.partnerCompany || !formData.product || !formData.quantity || !formData.vehicleNo) {
|
|
|
- showToast({ type: 'fail', message: '请填写必填项' })
|
|
|
+ if (
|
|
|
+ !formData.date ||
|
|
|
+ !formData.partnerCompany ||
|
|
|
+ !formData.productId ||
|
|
|
+ !formData.quantity ||
|
|
|
+ !formData.vehicleNo
|
|
|
+ ) {
|
|
|
+ showToast({ type: 'fail', message: '请填写必填项(含产品与车牌)' })
|
|
|
return
|
|
|
}
|
|
|
|
|
|
saving.value = true
|
|
|
try {
|
|
|
- if (formData.vehicleNo?.trim()) {
|
|
|
- await ensureVehicleExists(formData.vehicleNo.trim())
|
|
|
- }
|
|
|
if (editingRecord.value) {
|
|
|
await inventoryApi.update(editingRecord.value.id, formData)
|
|
|
showToast({ type: 'success', message: '更新成功' })
|
|
|
@@ -440,7 +251,6 @@ const handleSubmit = async () => {
|
|
|
await inventoryApi.create(formData)
|
|
|
showToast({ type: 'success', message: '创建成功' })
|
|
|
}
|
|
|
- // 根据来源跳转到对应页面(兼容 from=plan / from=plans)
|
|
|
const from = route.query.from as string
|
|
|
if (from === 'plan' || from === 'plans') {
|
|
|
router.push('/plans')
|
|
|
@@ -455,7 +265,6 @@ const handleSubmit = async () => {
|
|
|
}
|
|
|
|
|
|
const handleBack = () => {
|
|
|
- // 根据来源跳转到对应页面(兼容 from=plan / from=plans)
|
|
|
const from = route.query.from as string
|
|
|
if (from === 'plan' || from === 'plans') {
|
|
|
router.push('/plans')
|
|
|
@@ -464,28 +273,7 @@ const handleBack = () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// 监听公司选择器弹窗,当打开时重置搜索关键词
|
|
|
-watch(showCompanyPicker, (newVal) => {
|
|
|
- if (newVal) {
|
|
|
- companySearchKeyword.value = ''
|
|
|
- }
|
|
|
-})
|
|
|
-
|
|
|
-watch(showCargoPicker, (newVal) => {
|
|
|
- if (newVal) {
|
|
|
- cargoSearchKeyword.value = ''
|
|
|
- }
|
|
|
-})
|
|
|
-
|
|
|
-watch(showVehiclePicker, (newVal) => {
|
|
|
- if (newVal) {
|
|
|
- vehicleSearchKeyword.value = ''
|
|
|
- loadVehicles()
|
|
|
- }
|
|
|
-})
|
|
|
-
|
|
|
onMounted(async () => {
|
|
|
- await Promise.all([loadCompanies(), loadCargos()])
|
|
|
if (isEdit.value || isView.value) {
|
|
|
await loadData()
|
|
|
}
|
|
|
@@ -495,55 +283,6 @@ onMounted(async () => {
|
|
|
<style lang="scss" scoped>
|
|
|
@import '@/assets/styles/detail-page.scss';
|
|
|
|
|
|
-.company-picker-container {
|
|
|
- height: 100%;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
-}
|
|
|
-
|
|
|
-.search-container {
|
|
|
- padding: 16px;
|
|
|
- border-bottom: 1px solid #ebedf0;
|
|
|
- background: #fff;
|
|
|
-
|
|
|
- :deep(.van-search) {
|
|
|
- padding: 0;
|
|
|
- }
|
|
|
-
|
|
|
- :deep(.van-search__content) {
|
|
|
- background: #f7f8fa;
|
|
|
- border-radius: 20px;
|
|
|
- }
|
|
|
-
|
|
|
- :deep(.van-field__control) {
|
|
|
- font-size: 14px;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/* 不强制让 van-picker 占满剩余高度,使用 popup 的 max-height 控制总体高度 */
|
|
|
-
|
|
|
-.vehicle-picker-container {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- max-height: 70vh;
|
|
|
-}
|
|
|
-
|
|
|
-.vehicle-picker-container .vehicle-list {
|
|
|
- flex: 1;
|
|
|
- overflow-y: auto;
|
|
|
-}
|
|
|
-
|
|
|
-.vehicle-picker-container .manual-input-row {
|
|
|
- padding: 12px 16px;
|
|
|
- border-top: 1px solid #ebedf0;
|
|
|
- background: #fff;
|
|
|
-}
|
|
|
-
|
|
|
-.dialog-field {
|
|
|
- padding: 12px 16px;
|
|
|
-}
|
|
|
-
|
|
|
-/* 数量为默认值 32 时红色标识,提醒用户关注 */
|
|
|
.quantity-default-red .form-label,
|
|
|
.quantity-default-red .form-label .required {
|
|
|
color: var(--van-danger-color, #ee0a24);
|
|
|
@@ -553,4 +292,3 @@ onMounted(async () => {
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
</style>
|
|
|
-
|