ry_app/package_a/addProduct/addProduct.vue

626 lines
21 KiB
Vue
Raw Blame History

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="add-product-page">
<!-- 头部 -->
<view class="page-header">
<text class="header-title">添加商品</text>
</view>
<!-- 表单区域 -->
<view class="form-container">
<!-- 商品名称 -->
<view class="form-item">
<text class="item-label">商品名称</text>
<input
class="item-input"
type="text"
placeholder="名称"
v-model="productInfo.name"
/>
</view>
<!-- 条码/条码扫码 -->
<view class="form-item">
<text class="item-label">条码</text>
<view class="barcode-section">
<input
class="item-input barcode-input"
type="text"
placeholder="条码/条码扫码"
v-model="productInfo.barcode"
/>
<view class="scan-btn" @tap="handleScan">
<text class="scan-text">扫码</text>
</view>
</view>
</view>
<!-- 商品图 -->
<view class="form-item">
<text class="item-label">商品图</text>
<view class="image-upload-section">
<view v-if="!productInfo.image" class="upload-placeholder" @tap="chooseImage">
<text class="upload-icon">+</text>
<text class="upload-text">点击上传商品图片</text>
</view>
<view v-else class="image-wrapper">
<image
:src="productInfo.image"
class="product-image"
mode="aspectFit"
@tap="previewImage"
></image>
<view class="image-delete-btn" @tap.stop="deleteImage">
<text class="image-delete-icon">x</text>
</view>
<view class="image-reupload-btn" @tap.stop="chooseImage">
<text class="image-reupload-text">替换</text>
</view>
</view>
</view>
</view>
<view class="divider"></view>
<!-- 售价 -->
<view class="form-item">
<text class="item-label">售价</text>
<view class="price-section">
<input
class="item-input price-input"
type="digit"
placeholder="5.0"
v-model="productInfo.price"
/>
<text class="price-unit">元</text>
</view>
</view>
<!-- 推荐价格 -->
<view class="recommend-price-section">
<text class="recommend-title">推荐价格(点击快速设置售价)</text>
<view class="recommend-list">
<view class="recommend-item" @tap="setPrice(5.0)">
<text class="recommend-percent">82%商家卖</text>
<text class="recommend-price">5.0元</text>
</view>
<view class="recommend-item" @tap="setPrice(5.5)">
<text class="recommend-percent">9%商家卖</text>
<text class="recommend-price">5.5元</text>
</view>
<view class="recommend-item" @tap="setPrice(4.5)">
<text class="recommend-percent">2%商家卖</text>
<text class="recommend-price">4.5元</text>
</view>
</view>
</view>
<view class="divider"></view>
<!-- 进货数量 -->
<view class="form-item">
<text class="item-label">进货数量</text>
<view class="quantity-section">
<input
class="item-input"
type="number"
placeholder="请输入进货数量"
v-model="productInfo.quantity"
/>
<text class="quantity-unit">件</text>
</view>
</view>
<!-- 进货价 -->
<view class="form-item">
<text class="item-label">进货价</text>
<view class="cost-section">
<input
class="item-input"
type="digit"
placeholder="请输入进货价"
v-model="productInfo.cost"
/>
<text class="cost-unit">元</text>
</view>
</view>
<!-- ✅ 保质期管理 - 红色滑动开关 完美匹配截图 -->
<view class="form-item">
<text class="item-label">保质期管理</text>
<switch
:checked="productInfo.expirationManagement"
@change="toggleExpirationManagement"
color="#F53F3F"
/>
</view>
<!-- 保质期天数 -->
<view class="form-item" v-if="productInfo.expirationManagement">
<text class="item-label">保质期天数</text>
<picker
mode="selector"
:range="expirationLabels"
:value="expirationIndex"
@change="onExpirationChange"
>
<view class="picker-value">
<text :class="expirationIndex >= 0 ? 'picker-text' : 'picker-placeholder'">
{{ expirationIndex >= 0 ? expirationOptions[expirationIndex].label : '请选择保质期天数' }}
</text>
<text class="picker-arrow">&#9662;</text>
</view>
</picker>
</view>
<!-- 临期提醒天数 -->
<view class="form-item" v-if="productInfo.expirationManagement">
<text class="item-label">临期提醒天数</text>
<view class="warning-days-section">
<input
class="item-input"
type="number"
placeholder="请输入临期提醒天数"
v-model="productInfo.warningDays"
/>
<text class="warning-days-unit">天</text>
</view>
</view>
<!-- 生产日期 -->
<view class="form-item" v-if="productInfo.expirationManagement">
<text class="item-label">生产日期</text>
<uni-datetime-picker
type="date"
v-model="productInfo.productionDate"
:start="startDate"
:end="endDate"
placeholder="请选择生产日期"
/>
</view>
<!-- 货架码 -->
<view class="form-item">
<text class="item-label">货架码</text>
<input
class="item-input"
type="text"
placeholder="请输入货架码"
v-model="productInfo.shelfCode"
/>
</view>
<!-- 商品编码 -->
<view class="form-item">
<text class="item-label">商品编码</text>
<input
class="item-input"
type="text"
placeholder="请输入商品编码"
v-model="productInfo.productCode"
/>
</view>
</view>
<!-- 保存按钮 -->
<view class="save-button-container">
<button class="save-button" @tap="handleSave">保存</button>
</view>
<!-- 微信小程序全屏扫码覆盖层 -->
<!-- #ifdef MP-WEIXIN -->
<view v-if="showScanner" class="scanner-overlay">
<camera
class="scanner-camera"
mode="scanCode"
device-position="back"
flash="off"
@scancode="onOverlayScanResult"
>
<cover-view class="scanner-mask">
<cover-view class="scanner-box">
<cover-view class="box-corner tl"></cover-view>
<cover-view class="box-corner tr"></cover-view>
<cover-view class="box-corner bl"></cover-view>
<cover-view class="box-corner br"></cover-view>
</cover-view>
<cover-view class="scanner-actions">
<cover-view class="scanner-btn" @tap="closeScanner"></cover-view>
</cover-view>
</cover-view>
</camera>
</view>
<!-- #endif -->
</view>
</template>
<script>
import { addProductWithFile } from '@/api/product'
import { getStoreId } from '@/utils/auth'
import apiUrl from '@/utils/api'
export default {
data() {
return {
productInfo: {
name: '',
barcode: '',
image: '',
price: '',
quantity: '',
cost: '',
expirationManagement: false,
shelfCode: '',
shelfLife:'',
productCode: '',
expirationDays: '',
warningDays: '',
productionDate: '',
url:''
},
showScanner: false,
expirationOptions: [
{ label: '6个月180天', days: 180 },
{ label: '8个月240天', days: 240 },
{ label: '9个月270天', days: 270 },
{ label: '10个月300天', days: 300 },
{ label: '1年365天', days: 365 }
],
expirationIndex: -1
}
},
computed: {
expirationLabels() {
return this.expirationOptions.map(item => item.label);
},
warningDaysDisplay() {
if (!this.productInfo.expirationDays) return '';
const days = parseInt(this.productInfo.expirationDays);
return days.toString().substring(0, 2);
},
startDate() {
return '2020-01-01';
},
endDate() {
const now = new Date();
const year = now.getFullYear();
const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getDate().toString().padStart(2, '0');
return `${year}-${month}-${day}`;
}
},
onLoad(options) {
console.log('页面参数:', options,apiUrl);
this.url = apiUrl
// 从扫码页面传入的条形码
if (options && options.fromData) {
try {
this.productInfo.barcode = options.barcode
// 使用 decodeURIComponent 解码乱码数据
const decodedData = decodeURIComponent(options.fromData);
console.log('解码数据',decodedData);
const fromData = JSON.parse(decodedData)[0];
this.productInfo.name = fromData.productName || '';
this.productInfo.cost = fromData.costPrice || '';
this.productInfo.barcode = fromData.productBarCode || '';
this.productInfo.price = fromData.storePrice || '';
this.productInfo.quantity = fromData.stockQuantity || '';
this.productInfo.productCode = fromData.productCode || '';
this.productInfo.image = this.url + fromData.mainImage || '';
this.productInfo.shelfCode = fromData.shelfCode || '';
this.productInfo.expirationManagement = fromData.shelfLife?true:false;
if(fromData.shelfLife){
console.log(fromData.productionDate);
this.productInfo.productionDate = fromData.productionDate || '2026-12-19'
this.productInfo.expirationDays = fromData.shelfLife;
this.productInfo.shelfLife = fromData.shelfLife;
this.productInfo.warningDays = fromData.approaching || fromData.shelfLife.toString().substring(0, 2);
// 同步下拉框选中索引
const idx = this.expirationOptions.findIndex(item => item.days === Number(fromData.shelfLife));
if (idx >= 0) this.expirationIndex = idx;
}
console.log(this.productInfo);
} catch (e) {
this.productInfo.barcode = options.barcode
}
}
if (options.fromData) {
try {
} catch (error) {
console.error('解析商品数据失败:', error);
}
}
},
methods: {
handleScan() {
// #ifdef MP-WEIXIN
this.showScanner = true
// #endif
// #ifndef MP-WEIXIN
uni.scanCode({
onlyFromCamera: true,
scanType: ['barCode'],
success: (res) => {
const code = (res.result || '').trim()
const type = (res.scanType || res.codeType || '').toString().toUpperCase()
const isBarcode = type.indexOf('QR') === -1
const numericOk = /^\d{8,20}$/.test(code)
if (isBarcode || numericOk) {
this.productInfo.barcode = code
uni.showToast({ title: '扫码成功', icon: 'success' })
} else {
uni.showToast({ title: '请扫条形码', icon: 'none' })
}
},
fail: () => {
uni.showToast({ title: '扫码失败', icon: 'none' })
}
})
// #endif
},
closeScanner() {
this.showScanner = false
},
onOverlayScanResult(e) {
if (!this.showScanner) return
const { result, scanType } = e.detail || {}
const code = (result || '').trim()
const type = (scanType || '').toString().toUpperCase()
const isBarcode = type.indexOf('QR') === -1
const numericOk = /^\d{8,20}$/.test(code)
if (isBarcode || numericOk) {
this.productInfo.barcode = code
this.showScanner = false
uni.showToast({ title: '扫码成功', icon: 'success' })
} else {
uni.showToast({ title: '请扫条形码', icon: 'none' })
}
},
// 预览图片
previewImage() {
if (this.productInfo.image) {
uni.previewImage({
current: this.productInfo.image,
urls: [this.productInfo.image]
})
}
},
// 选择图片
chooseImage() {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
this.productInfo.image = res.tempFilePaths[0]
}
})
},
// 删除图片
deleteImage() {
uni.showModal({
title: '提示',
content: '确定要删除商品图片吗?',
success: (res) => {
if (res.confirm) {
this.productInfo.image = ''
}
}
})
},
// 设置推荐价格
setPrice(price) {
this.productInfo.price = price.toString()
uni.showToast({ title: `已设置为${price}`, icon: 'success', duration: 1500 })
},
// ✅ 切换保质期管理-适配红色滑动开关 极简写法
toggleExpirationManagement(e) {
this.productInfo.expirationManagement = e.detail.value
},
// 下拉框选择保质期天数
onExpirationChange(e) {
const index = e.detail.value;
this.expirationIndex = index;
const selected = this.expirationOptions[index];
this.productInfo.expirationDays = selected.days.toString();
const warningDays = selected.days.toString().substring(0, 2);
this.productInfo.warningDays = warningDays;
},
// 表单验证-完整校验
validateForm() {
if (!this.productInfo.name.trim()) { uni.showToast({ title: '请输入商品名称', icon: 'none' });return false }
if (!this.productInfo.price) { uni.showToast({ title: '请输入售价', icon: 'none' });return false }
// 开启保质期管理 必校验三项
if (this.productInfo.expirationManagement) {
if (!this.productInfo.expirationDays) { uni.showToast({ title: '请选择保质期天数', icon: 'none' });return false }
// 临期提醒天数自动设置,不需要手动输入
if (!this.productInfo.productionDate) { uni.showToast({ title: '请选择生产日期', icon: 'none' });return false }
}
return true
},
// 保存商品-完整传参
async handleSave() {
if (!this.validateForm()) return
const storeId = getStoreId();
if (!storeId) { uni.showToast({ title: '请先选择门店', icon: 'none' });return }
uni.showLoading({ title: '保存中...' });
try {
const formData = {
productName: this.productInfo.name,
productBarCode: this.productInfo.barcode,
storePrice: this.productInfo.price,
stockQuantity: this.productInfo.quantity,
costPrice: this.productInfo.cost,
productCode: this.productInfo.productCode,
shelfCode: this.productInfo.shelfCode,
expirationManagement: this.productInfo.expirationManagement ? 1 : 0,
shelfLife: this.productInfo.expirationDays,
productionDate: this.productInfo.productionDate,
approaching: this.productInfo.warningDays,
storeId: storeId
};
console.log('gggg',formData)
const res = await addProductWithFile(this.productInfo.image || '', formData);
uni.hideLoading();
if (res.code === 200) {
uni.showToast({ title: '商品添加成功', icon: 'success', duration: 2000 });
setTimeout(() => { uni.navigateBack({ delta: 1 }); }, 2000);
} else {
uni.showToast({ title: res.msg || '商品添加失败', icon: 'none' });
}
} catch (error) {
uni.hideLoading();
console.error('添加商品失败:', error);
uni.showToast({ title: '网络请求失败', icon: 'none' });
}
}
}
}
</script>
<style scoped>
.add-product-page {
background-color: #f5f5f5;
min-height: 100vh;
padding-bottom: 120rpx;
}
/* 头部 */
.page-header {
background-color: #ffffff;
padding: 40rpx 32rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.header-title { font-size: 36rpx;font-weight: 600;color: #333333; }
/* 表单容器 */
.form-container { background-color: #ffffff;margin-top: 20rpx; }
/* 表单项 */
.form-item {
padding: 32rpx;
border-bottom: 1rpx solid #f0f0f0;
display: flex;
align-items: center;
justify-content: space-between;
}
.item-label { font-size: 32rpx;color: #333333;font-weight: 500;min-width: 160rpx; }
.item-input { flex:1;font-size:32rpx;color:#333;text-align:right;padding:0 20rpx; }
.item-input::placeholder { color: #999999; }
/* 条码区域 */
.barcode-section { display:flex;align-items:center;flex:1; }
.barcode-input { flex:1;margin-right:20rpx; }
.scan-btn { background:#07C160;padding:16rpx 32rpx;border-radius:8rpx; }
.scan-text { color:#fff;font-size:28rpx; }
/* 图片上传区域 */
.image-upload-section { flex:1;display:flex;justify-content:flex-end; }
.upload-placeholder { width:200rpx;height:200rpx;border:2rpx dashed #ccc;border-radius:8rpx;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#f9f9f9; }
.upload-icon { font-size:48rpx;color:#999;margin-bottom:16rpx; }
.upload-text { font-size:24rpx;color:#999; }
.image-wrapper { position:relative;width:200rpx;height:200rpx; }
.product-image { width:200rpx;height:200rpx;border-radius:8rpx; }
.image-delete-btn { position:absolute;top:-16rpx;right:-16rpx;width:40rpx;height:40rpx;background:#F53F3F;border-radius:50%;display:flex;align-items:center;justify-content:center;z-index:10; }
.image-delete-icon { color:#fff;font-size:26rpx;line-height:40rpx; }
.image-reupload-btn { position:absolute;bottom:0;left:0;right:0;height:48rpx;background:rgba(0,0,0,0.5);border-radius:0 0 8rpx 8rpx;display:flex;align-items:center;justify-content:center; }
.image-reupload-text { color:#fff;font-size:22rpx; }
/* 价格区域 */
.price-section { display:flex;align-items:center;flex:1;justify-content:flex-end; }
.price-input { flex:none;width:120rpx;text-align:center; }
.price-unit { font-size:32rpx;color:#333;margin-left:10rpx; }
/* 推荐价格 */
.recommend-price-section { padding:32rpx;border-bottom:1rpx solid #f0f0f0; }
.recommend-title { font-size:28rpx;color:#666;margin-bottom:24rpx;display:block; }
.recommend-list { display:flex;justify-content:space-between; }
.recommend-item { display:flex;flex-direction:column;align-items:center;padding:20rpx 24rpx;background:#f9f9f9;border-radius:8rpx;flex:1;margin:0 10rpx; }
.recommend-percent { font-size:26rpx;color:#666;margin-bottom:8rpx; }
.recommend-price { font-size:30rpx;color:#07C160;font-weight:500; }
/* 数量/进货价 单位 */
.quantity-section,.cost-section { display:flex;align-items:center;flex:1;justify-content:flex-end; }
.quantity-unit,.cost-unit { font-size:32rpx;color:#333;margin-left:10rpx; }
/* 保质期下拉框样式 */
.picker-value { display:flex;align-items:center;justify-content:flex-end;padding:0 20rpx;min-width:280rpx; }
.picker-text { font-size:30rpx;color:#333; }
.picker-placeholder { font-size:30rpx;color:#999; }
.picker-arrow { font-size:24rpx;color:#999;margin-left:12rpx; }
/* 临期提醒天数样式 */
.warning-days-section { display:flex;align-items:center;flex:1;justify-content:flex-end; }
.warning-days-unit { font-size:32rpx;color:#333;margin-left:10rpx; }
/* 分隔线 */
.divider { height:20rpx;background:#f5f5f5;border-top:1rpx solid #f0f0f0;border-bottom:1rpx solid #f0f0f0; }
/* 保存按钮 */
.save-button-container { position:fixed;bottom:0;left:0;right:0;background:#fff;padding:20rpx 32rpx;border-top:1rpx solid #f0f0f0; }
.save-button { background:#F53F3F;color:#ffffff;font-size:34rpx;height:88rpx;line-height:88rpx;border-radius:8rpx; }
.save-button::after { border:none; }
/* 响应式调整 */
@media (max-width:750px) {
.recommend-list { flex-direction:column; }
.recommend-item { margin:10rpx 0; }
}
/* 微信小程序全屏扫码覆盖层样式 */
.scanner-overlay {
position: fixed;
left: 0; right: 0; top: 0; bottom: 0;
background: #000;
z-index: 9999;
}
.scanner-camera {
width: 100%;
height: 100%;
}
.scanner-mask {
position: absolute;
left: 0; top: 0; right: 0; bottom: 0;
}
.scanner-box {
position: fixed;
left: 50%;
top: 50%;
width: 520rpx;
height: 520rpx;
transform: translate(-50%, -50%);
}
.box-corner {
position: absolute;
width: 40rpx;
height: 40rpx;
border: 4rpx solid #fff;
}
.box-corner.tl { left: 0; top: 0; border-right: none; border-bottom: none; }
.box-corner.tr { right: 0; top: 0; border-left: none; border-bottom: none; }
.box-corner.bl { left: 0; bottom: 0; border-right: none; border-top: none; }
.box-corner.br { right: 0; bottom: 0; border-left: none; border-top: none; }
.scanner-actions {
position: absolute;
left: 0; right: 0; bottom: 80rpx;
display: flex; justify-content: center;
}
.scanner-btn {
padding: 20rpx 40rpx;
color: #fff;
border: 2rpx solid rgba(255,255,255,0.6);
border-radius: 40rpx;
background: rgba(0,0,0,0.35);
}
</style>