214 lines
7.2 KiB
Vue
214 lines
7.2 KiB
Vue
<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>
|
||
|
||
|