Files
PartsInquiry/frontend/pages/product/form.vue
2025-09-17 14:40:16 +08:00

214 lines
7.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<scroll-view scroll-y class="page">
<view class="card">
<view class="row">
<text class="label">商品名称</text>
<input v-model.trim="form.name" placeholder="必填" />
</view>
<view class="row">
<text class="label">条形码</text>
<input v-model.trim="form.barcode" placeholder="可扫码或输入" />
<!-- #ifdef APP-PLUS -->
<button size="mini" @click="scan">扫码</button>
<!-- #endif -->
</view>
<view class="row">
<text class="label">品牌/型号/规格/产地</text>
</view>
<view class="row">
<input v-model.trim="form.brand" placeholder="品牌" />
</view>
<view class="row">
<input v-model.trim="form.model" placeholder="型号" />
</view>
<view class="row">
<input v-model.trim="form.spec" placeholder="规格" />
</view>
<view class="row">
<input v-model.trim="form.origin" placeholder="产地" />
</view>
<view class="row">
<picker mode="selector" :range="unitNames" @change="onPickUnit">
<view class="picker">主单位{{ unitLabel }}</view>
</picker>
<picker mode="selector" :range="categoryNames" @change="onPickCategory">
<view class="picker">类别{{ categoryLabel }}</view>
</picker>
</view>
</view>
<view class="card">
<view class="row">
<text class="label">库存与安全库存</text>
</view>
<view class="row">
<input type="number" v-model.number="form.stock" placeholder="当前库存" />
<input type="number" v-model.number="form.safeMin" placeholder="安全库存下限" />
<input type="number" v-model.number="form.safeMax" placeholder="安全库存上限" />
</view>
</view>
<view class="card">
<view class="row">
<text class="label">价格进价/零售/批发/大单</text>
</view>
<view class="row prices">
<input type="number" v-model.number="form.purchasePrice" placeholder="进货价" />
<input type="number" v-model.number="form.retailPrice" placeholder="零售价" />
<input type="number" v-model.number="form.wholesalePrice" placeholder="批发价" />
<input type="number" v-model.number="form.bigClientPrice" placeholder="大单价" />
</view>
</view>
<view class="card">
<text class="label">图片</text>
<ImageUploader v-model="form.images" :formData="{ ownerType: 'product' }" />
</view>
<view class="card">
<text class="label">备注</text>
<textarea v-model.trim="form.remark" placeholder="可选" auto-height />
</view>
<view class="fixed">
<button type="default" @click="save(false)">保存</button>
<button type="primary" @click="save(true)">保存并继续</button>
</view>
</scroll-view>
</template>
<script>
import ImageUploader from '../../components/ImageUploader.vue'
import { get, post, put } from '../../common/http.js'
export default {
components: { ImageUploader },
data() {
return {
id: '',
form: {
name: '', barcode: '', brand: '', model: '', spec: '', origin: '',
categoryId: '', unitId: '',
stock: null, safeMin: null, safeMax: null,
purchasePrice: null, retailPrice: null, wholesalePrice: null, bigClientPrice: null,
images: [], remark: ''
},
units: [],
categories: []
}
},
onLoad(query) {
this.id = query?.id || ''
this.bootstrap()
},
computed: {
unitNames() { return this.units.map(u => u.name) },
categoryNames() { return this.categories.map(c => c.name) },
unitLabel() {
const u = this.units.find(x => String(x.id) === String(this.form.unitId))
return u ? u.name : '选择单位'
},
categoryLabel() {
const c = this.categories.find(x => String(x.id) === String(this.form.categoryId))
return c ? c.name : '选择类别'
}
},
methods: {
async bootstrap() {
await Promise.all([this.fetchUnits(), this.fetchCategories()])
if (this.id) this.loadDetail()
},
async fetchUnits() {
try {
const res = await get('/api/product-units')
this.units = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
} catch (_) {}
},
async fetchCategories() {
try {
const res = await get('/api/product-categories')
this.categories = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
} catch (_) {}
},
onPickUnit(e) {
const idx = Number(e.detail.value); const u = this.units[idx]
this.form.unitId = u ? u.id : ''
},
onPickCategory(e) {
const idx = Number(e.detail.value); const c = this.categories[idx]
this.form.categoryId = c ? c.id : ''
},
scan() {
uni.scanCode({ onlyFromCamera: false, success: (res) => {
this.form.barcode = res.result
}})
},
async loadDetail() {
try {
const data = await get('/api/products/' + this.id)
Object.assign(this.form, {
name: data.name,
barcode: data.barcode, brand: data.brand, model: data.model, spec: data.spec, origin: data.origin,
categoryId: data.categoryId, unitId: data.unitId,
stock: data.stock,
safeMin: data.safeMin, safeMax: data.safeMax,
purchasePrice: data.purchasePrice, retailPrice: data.retailPrice,
wholesalePrice: data.wholesalePrice, bigClientPrice: data.bigClientPrice,
images: (data.images || []).map(i => i.url || i)
})
} catch (_) {}
},
validate() {
if (!this.form.name) { uni.showToast({ title: '请填写名称', icon: 'none' }); return false }
if (this.form.safeMin != null && this.form.safeMax != null && Number(this.form.safeMin) > Number(this.form.safeMax)) {
uni.showToast({ title: '安全库存区间不合法', icon: 'none' }); return false
}
return true
},
buildPayload() {
const f = this.form
return {
name: f.name, barcode: f.barcode, brand: f.brand, model: f.model, spec: f.spec, origin: f.origin,
categoryId: f.categoryId || null, unitId: f.unitId,
safeMin: f.safeMin, safeMax: f.safeMax,
prices: {
purchasePrice: f.purchasePrice, retailPrice: f.retailPrice, wholesalePrice: f.wholesalePrice, bigClientPrice: f.bigClientPrice
},
stock: f.stock,
images: f.images,
remark: f.remark
}
},
async save(goOn) {
if (!this.validate()) return
const payload = this.buildPayload()
try {
if (this.id) await put('/api/products/' + this.id, payload)
else await post('/api/products', payload)
uni.showToast({ title: '保存成功', icon: 'success' })
if (goOn && !this.id) {
this.form = { name: '', barcode: '', brand: '', model: '', spec: '', origin: '', categoryId: '', unitId: '', stock: null, safeMin: null, safeMax: null, purchasePrice: null, retailPrice: null, wholesalePrice: null, bigClientPrice: null, images: [], remark: '' }
} else {
setTimeout(() => uni.navigateBack(), 400)
}
} catch (e) {
uni.showToast({ title: '保存失败', icon: 'none' })
}
}
}
}
</script>
<style>
.page { background:#f6f6f6; height: 100vh; }
.card { background:#fff; margin: 16rpx; padding: 16rpx; border-radius: 12rpx; }
.row { display:flex; gap: 12rpx; align-items: center; margin-bottom: 12rpx; }
.label { width: 180rpx; color:#666; }
.row input { flex:1; background:#f7f7f7; border-radius: 10rpx; padding: 12rpx; }
.picker { padding: 8rpx 12rpx; background:#f0f0f0; border-radius: 10rpx; color:#666; margin-left: 8rpx; }
.prices input { width: 30%; }
.fixed { position: fixed; left: 0; right: 0; bottom: 0; background:#fff; padding: 12rpx 16rpx; display:flex; gap: 16rpx; }
</style>