feat:共富商品管理新增和编辑

This commit is contained in:
weiwei committed 2025-09-11 19:59:35 +08:00
1 parent a64cda2312
commit 95d57a9262
4 files changed
+329 -70

No files matched your search

+221 -34
View File
@@ -1,22 +1,31 @@
<template>
<crud-layout title="" mainIsScroll bg="/static/page/blue-bg.png">
<view class="h-30rpx" />
<tm-form @submit="onsubmit" v-model="form" :rules="rules" direction="vertical">
<tm-form @submit="onsubmit" v-model="form" :rules="rules" direction="horizontal">
<view class="card p-20rpx mb-20rpx">
<view class="prefix_title mb-2">商品基本信息</view>
<tm-form-item name="title" label="商品标题" direction="horizontal">
<tm-form-item name="title" label="商品标题">
<tm-input v-model="form.title" type="text" :showClear="true" placeholder="请输入商品标题"></tm-input>
</tm-form-item>
<tm-form-item name="unit" label="计量单位" direction="horizontal">
<tm-form-item name="unit" label="计量单位">
<tm-input v-model="form.unit" type="text" :showClear="true" placeholder="请输入计量单位"></tm-input>
</tm-form-item>
<tm-form-item name="status" label="状态" direction="horizontal">
<tm-form-item name="categoryId" label="分类" @click="selectCategary">
<tm-input v-model="form.categoryName" placeholder="请选择商品分类" style="pointer-events: none" disabled />
<!-- color="transparent" -->
</tm-form-item>
<tm-form-item name="status" label="状态">
<tm-radio-group v-model="form.status" align="flex-end">
<tm-radio :value="index.toString()" :label="item" v-for="(item, index) in ['上架', '下架']" :key="index"></tm-radio>
</tm-radio-group>
</tm-form-item>
<tm-form-item name="saleStartTime" label="开售时间" direction="horizontal">
<tm-picker-date v-model="form.saleStartTime" v-model:modelStr="_saleStartTimeStr">
<tm-form-item name="saleStartTime" label="开售时间">
<tm-picker-date
v-model="form.saleStartTime"
v-model:modelStr="_saleStartTimeStr"
start="2024-01-01 00:00:00"
end="2090-01-01 00:00:00"
>
<tm-input
:modelValue="_saleStartTimeStr"
placeholder="请选择开售时间"
@@ -31,8 +40,13 @@
</tm-input>
</tm-picker-date>
</tm-form-item>
<tm-form-item name="saleEndTime" label="停售时间" direction="horizontal">
<tm-picker-date v-model="form.saleEndTime" v-model:modelStr="_saleEndTimeStr">
<tm-form-item name="saleEndTime" label="停售时间">
<tm-picker-date
v-model="form.saleEndTime"
v-model:modelStr="_saleEndTimeStr"
start="2024-01-01 00:00:00"
end="2090-01-01 00:00:00"
>
<tm-input
:modelValue="_saleEndTimeStr"
placeholder="请选择停售时间"
@@ -53,52 +67,225 @@
:maxFileSize="1024 * 1024 * 5"
:url="'/api/admin/sys-file/upload'"
:header="hooks.useDefaultHeader()"
:column="2"
img-height="300"
:column="1.2"
img-height="360"
:max-count="1"
/>
</tm-form-item>
<view class="sub-text-20 c-red! mb-2">注:图片大小不超过5M</view>
<tm-form-item name="description" label="详情描述">
<tm-form-item name="description" label="详情描述" direction="vertical" required="">
<tm-input v-model="form.description" type="textarea" :showClear="false" placeholder="请输入详情描述"></tm-input>
</tm-form-item>
<tm-form-item name="priceType" label="定价方式">
<tm-radio-group v-model="form.priceType" align="flex-end">
<tm-radio
:value="item.value"
:label="item.label"
v-for="(item, index) in [
{ label: '多SKU模式', value: '1' },
{ label: '单价SPU模式', value: '2' },
]"
:key="index"
></tm-radio>
</tm-radio-group>
</tm-form-item>
<view v-if="form.priceType == '2'">
<tm-form-item name="spuPrice" label="SPU售价(元)" labelWidth="200">
<view class="flex justify-end">
<tm-stepper width="300" v-model="form.spuPrice"></tm-stepper>
</view>
</tm-form-item>
<tm-form-item name="originalPrice" label="划线价(元)" labelWidth="200">
<view class="flex justify-end">
<tm-stepper width="300" v-model="form.originalPrice"></tm-stepper>
</view>
</tm-form-item>
</view>
</view>
<view v-if="form.priceType == '1'" class="card p-20rpx mb-20rpx">
<view class="prefix_title mb-2">商品SKU信息</view>
<view v-for="(item, index) in form.skuList" :key="index">
<view v-if="index !== 0" class="my-4 h-3rpx bg-#A0A0A044"></view>
<view class="flex justify-between items-center mb-2">
<text>编号:{{ index + 1 }}</text>
<tm-button v-if="index !== 0" class="px-2" size="xs" skin="thin" @click.stop="delSkuListItem(index)">删 除</tm-button>
</view>
<tm-form-item name="skuList[index].skuCode" label="SKU编码">
<tm-input v-model="item.skuCode" placeholder="请输入SKU编码"></tm-input>
</tm-form-item>
<tm-form-item name="skuList[index].skuName" label="SKU名称">
<tm-input v-model="item.skuName" placeholder="请输入SKU名称"></tm-input>
</tm-form-item>
<tm-form-item name="skuList[index].barcode" label="条码">
<tm-input v-model="item.barcode" placeholder="请输入条码"></tm-input>
</tm-form-item>
<tm-form-item name="skuList[index].price" label="售价(元)">
<view class="flex justify-end">
<tm-stepper width="300" v-model="item.price"></tm-stepper>
</view>
</tm-form-item>
<tm-form-item name="skuList[index].stock" label="库存">
<view class="flex justify-end">
<tm-stepper width="300" v-model="item.stock"></tm-stepper>
</view>
</tm-form-item>
<tm-form-item name="skuList[index].weightG" label="重量(克)">
<view class="flex justify-end">
<tm-stepper width="300" v-model="item.weightG"></tm-stepper>
</view>
</tm-form-item>
<tm-form-item name="skuList[index].status" label="状态">
<tm-radio-group v-model="item.status" align="flex-end">
<tm-radio :value="index.toString()" :label="item" v-for="(item, index) in ['启用', '禁用']" :key="index"></tm-radio>
</tm-radio-group>
</tm-form-item>
</view>
<tm-button block size="s" skin="dashed" color="#E5F1FF" @click.stop="addSkuListItem()">添 加</tm-button>
</view>
<tm-button form-type="submit" block>提交</tm-button>
</tm-form>
<view class="card p-x-20rpx p-y-30rpx m-b-20rpx">
<tm-picker
rang-key="title"
rang-text="title"
v-model="applyForm.reason"
v-model:model-str="applyForm.reasonStr"
:list="state.selectList"
>
<view class="flex-row items-center justify-between">
<view class="sub-text-28">申请原因</view>
<view class="flex-row items-center second-text-24">
<text>{{ applyForm.reasonStr || "请选择申请原因" }} </text>
<text class="m-l-10rpx color-#b1b1b1 iconfont icon-xiangxiajiantouxiao" />
</view>
</view>
</tm-picker>
</view>
<tm-drawer
title="请选择分类"
:showClose="true"
:showFooter="false"
size="auto"
placement="bottom"
v-model:show="showCategaryPicker"
:duration="100"
zIndex="100"
>
<shop-categary-picker :categoryTree="categoryTree" @confirm="onCategaryConfirm" @cancel="showCategaryPicker = false" />
</tm-drawer>
<view class="h-30rpx" />
<template #main-bottom>
<view class="bg-#0094ff py-20rpx text-center rounded-40rpx m-30rpx sub-text-30 color-white!" @tap="doSubmit"> 保存 </view>
</template>
</crud-layout>
</template>
<script setup lang="ts">
import { reactive, ref } from "vue"
import { hooks } from "@@/index"
import { prosperityShopApi } from "@/sheep/api"
import ShopCategaryPicker from "@/sheep/components/shop-categary-picker.vue"
import { onLoad } from "@dcloudio/uni-app"
const form = reactive<any>({})
const form = reactive<any>({
title: "",
unit: "",
categoryId: "",
categoryName: "",
status: "0",
saleStartTime: "",
saleEndTime: "",
coverImage: [],
description: "",
priceType: "1", // 定价方式(1:多SKU,2:单价SPU)
spuPrice: 0,
originalPrice: 0,
skuList: [
{
skuCode: "",
skuName: "",
price: 0,
costPrice: 0,
stock: 0,
barcode: "",
weightG: 0,
volumeCm3: 0,
status: "0",
},
],
})
const _saleStartTimeStr = ref("")
const _saleEndTimeStr = ref("")
const showCategaryPicker = ref(false)
const categoryTree = ref([])
const rules: Record<string, TM.FORM_RULE[]> = {
title: [{ required: true, message: "请输入商品标题" }],
unit: [{ required: true, message: "请输入商品单位" }],
categoryId: [{ required: true, message: "请选择商品分类" }],
status: [{ required: true, message: "请选择状态" }],
saleStartTime: [{ required: true, message: "请选择售卖开始时间" }],
saleEndTime: [{ required: true, message: "请选择售卖结束时间" }],
coverImage: [{ required: true, message: "请上传商品图" }],
}
onLoad((options: any) => {
const { id = "" } = options
if (!id) return
getGoodsDetail(id)
})
const getGoodsDetail = async (id: string) => {
const { code, data, msg } = await prosperityShopApi.getGoodsDetail(id)
if (code != 0 || !data) return uni.showToast({ title: msg || "商品详情获取失败", icon: "none" })
Object.assign(form, data, { status: data.status.toString(), priceType: data.priceType.toString() })
}
const selectCategary = () => {
getCategaryTree()
showCategaryPicker.value = true
}
const getCategaryTree = async () => {
const { code, data, msg } = await prosperityShopApi.getGoodsCategoryTree()
if (code != 0) return uni.showToast({ title: msg || "商品分类获取失败", icon: "none" })
categoryTree.value = data ?? []
}
const onCategaryConfirm = (category: { categoryId: string; categoryName: string }) => {
form.categoryId = category.categoryId
form.categoryName = category.categoryName
showCategaryPicker.value = false
}
const delSkuListItem = (index: number) => {
form.skuList.splice(index, 1)
}
const addSkuListItem = () => {
form.skuList.push({
skuCode: "",
skuName: "",
price: 0,
costPrice: 0,
stock: 0,
barcode: "",
weightG: 0,
volumeCm3: 0,
status: "0",
})
}
// 保存
const doSubmit = async () => {}
const onsubmit = async (evt: TM.FORM_SUBMIT_RESULT) => {
// console.log("校验结果:", evt)
console.log("提交的数据:", { ...form, coverImage: JSON.parse(form.coverImage[0].response).data.url })
if (!evt.isPass) return
if (form.priceType == "1") {
for (let i = 0; i < form.skuList.length; i++) {
const skuItem = form.skuList[i]
for (const key in skuItem) {
if (skuItem.hasOwnProperty(key) && skuItem[key] === "") return uni.showToast({ title: "请填写完整SKU信息", icon: "none" })
}
}
}
if (form.priceType == "2") {
form.skuList = []
}
try {
const { code, msg } = await prosperityShopApi.addGoods({ ...form, coverImage: JSON.parse(form.coverImage[0].response).data.url })
if (code != 0) return uni.showToast({ title: msg || "提交失败", icon: "none" })
uni.showToast({ title: "提交成功", icon: "success" })
uni.navigateBack()
} catch (error: any) {
uni.showToast({ title: error.message || "提交失败", icon: "none" })
}
}
</script>
+38 -34
View File
@@ -15,29 +15,25 @@
@scroll="pageHook.scroll"
>
<template #top>
<view class="w-full mt-2 flex items-center gap-2">
<tm-button size="m" @click="addGoods">新 增</tm-button>
<tm-button size="m">分 类</tm-button>
<tm-input
class="flex-1"
v-model="state.params.keyword"
type="text"
height="72"
minHeight="72"
fontSize="28"
:showClear="true"
:focus="false"
placeholder="请输入商品名称"
style="border: 4rpx solid #0779fd; border-radius: 14rpx"
:focus-highlight="false"
color="#ECF4F9"
@clear="inputClear"
>
<template v-slot:rightIcon>
<tm-button class="mr-1 px-3 py-26rpx text-30rpx!" size="xs" @click="search">搜 索</tm-button>
</template>
</tm-input>
</view>
<tm-input
class="mt-2"
v-model="state.params.keyword"
type="text"
height="72"
minHeight="72"
fontSize="28"
:showClear="true"
:focus="false"
placeholder="请输入商品名称"
style="border: 4rpx solid #0779fd; border-radius: 14rpx"
:focus-highlight="false"
color="#ECF4F9"
@clear="inputClear"
>
<template v-slot:rightIcon>
<tm-button class="mr-1 px-3 py-26rpx text-30rpx!" size="xs" @click="search">搜 索</tm-button>
</template>
</tm-input>
<scroll-view scroll-x>
<view class="w-full flex my-2">
<view
@@ -53,7 +49,7 @@
</scroll-view>
</template>
<view v-for="v in pageHook.list" :key="v.id" class="card mx-20rpx mb-2 px-4 py-2">
<view v-for="v in pageHook.list" :key="v.id" class="card mb-2 px-4 py-2">
<view class="flex">
<image
:src="filePath(v.coverImage)"
@@ -62,16 +58,16 @@
@click.stop="hooks.usePreviewImage(filePath(v.coverImage))"
/>
<view class="flex-1 ml-2 overflow-hidden">
<view class="truncate sub-text-34">{{ v.title }}</view>
<view class="sub-text-26 mt-4rpx">
<view class="truncate sub-text-32">{{ v.title }}</view>
<view class="sub-text-24 my-6rpx">
<text class="c-#0094ff! px-2 bg-#0094ff22 rounded-2 mr-2">
{{ state.category.find((item) => item.id == v.categoryId)?.name || "未知分类" }}
</text>
<text class="c-#0094ff! px-2 bg-#0094ff22 rounded-2">{{ v.status == "0" ? "在售" : "未售" }}</text>
</view>
<view class="mt-4rpx">
<text class="second-text-28 mr-2">单位 {{ v.unit }}</text>
<text class="second-text-28">库存 {{ v.totalStock }}</text>
<view>
<text class="second-text-26 mr-2">单位 {{ v.unit }}</text>
<text class="second-text-26">库存 {{ v.totalStock }}</text>
</view>
<view>
<text class="second-text-32 line-through mr-1">
@@ -80,10 +76,10 @@
</text>
<text class="color-red">
<text class="text-30rpx">¥</text>
<text class="text-46rpx font-bold">{{ v.spuPrice.toFixed(2).split(".")[0] }}.</text>
<text class="text-38rpx font-bold">{{ v.spuPrice.toFixed(2).split(".")[1] }}</text>
<text class="text-40rpx font-bold">{{ v.spuPrice.toFixed(2).split(".")[0] }}.</text>
<text class="text-32rpx font-bold">{{ v.spuPrice.toFixed(2).split(".")[1] }}</text>
</text>
<text class="view-clip px-1 text-20rpx c-white bg-red ml-1 rounded-r-1 rounded-lt-1">到手价</text>
<text class="view-clip px-1 text-18rpx c-white bg-red ml-1 rounded-r-1 rounded-lt-1">到手价</text>
</view>
</view>
</view>
@@ -101,6 +97,10 @@
</view>
</view>
</z-paging>
<view class="fixed bottom-80rpx left-40rpx flex-col gap-2">
<tm-button class="px-2" size="s" :linear="['to right', '#0779FD', '#62abff']" @click="addGoods">新增商品</tm-button>
<tm-button class="px-2" size="s" :linear="['to right', '#0779FD', '#62abff']">分类管理</tm-button>
</view>
</crud-layout>
</template>
@@ -109,7 +109,7 @@ import { reactive, ref } from "vue"
import { prosperityShopApi } from "@/sheep/api"
import { hooks } from "@/sheep"
import { filePath } from "@/sheep/utils/file"
import { onLoad } from "@dcloudio/uni-app"
import { onLoad, onShow } from "@dcloudio/uni-app"
const state = reactive<any>({
params: {
@@ -121,7 +121,7 @@ const state = reactive<any>({
})
const pageRef = ref<any>(null)
const pageHook = hooks.usePage(pageRef, prosperityShopApi.getGoodsList, null, "data.records")
const pageHook = hooks.usePage(pageRef, prosperityShopApi.getManageGoodsList, null, "data.records")
const query = (page: number, pagesize: number) => {
pageHook.startPage(page, pagesize, false, state.params)
@@ -131,6 +131,10 @@ onLoad(async () => {
await getGoodsCategory()
})
onShow(() => {
pageRef.value && pageRef.value.reload(false)
})
const getGoodsCategory = async () => {
const { code, data, msg } = await prosperityShopApi.getGoodsCategory()
if (code != 0) return uni.showToast({ title: msg || "商品分类获取失败", icon: "none" })
+18 -2
View File
@@ -25,7 +25,7 @@ export default {
})
},
// 获取超市商品分类列表树
getGoodsCategoryTree: (params: { merchantId: string }): Promise<any> => {
getGoodsCategoryTree: (params?: { merchantId: string }): Promise<any> => {
return request({
url: `/supermarket/app/mall/category/tree`,
method: "GET",
@@ -35,7 +35,7 @@ export default {
// 获取商品列表
getGoodsList: (params: any): Promise<any> => {
return request({
url: `/supermarket/app/mall/merchant/product/list`,
url: `/supermarket/app/mall/product/page`,
method: "GET",
params,
})
@@ -47,6 +47,14 @@ export default {
method: "GET",
})
},
// 共富商城商品管理获取自己商户的商品列表
getManageGoodsList: (params: any): Promise<any> => {
return request({
url: `/supermarket/app/mall/merchant/product/list`,
method: "GET",
params,
})
},
// 删除商品
deleteGoods: (id: string): Promise<any> => {
return request({
@@ -54,4 +62,12 @@ export default {
method: "DELETE",
})
},
// 新增商品
addGoods: (data: any): Promise<any> => {
return request({
url: `/supermarket/app/mall/merchant/product/create`,
method: "POST",
data,
})
},
}
@@ -0,0 +1,52 @@
<template>
<view class="max-h-[60vh] overflow-y-auto mb-20rpx">
<view
class="flex items-center p-2 bg-#eeeeee mb-2 mx-4 rounded-2 sub-text-26"
v-for="(item, index) in categoryTree"
:key="item.id"
:class="{ 'bg-#0779fd! c-white!': selectedCategary && selectedCategary.id === item.id }"
@click="onSelect(item)"
>
<view v-if="item.children.length" class="w-40rpx mr-2 text-center">
<!-- <text>{{ expandedIds.has(item.id) ? "▾" : "▸" }}</text> -->
<text>▾</text>
</view>
<view v-else class="w-40rpx mr-2" />
<view>{{ item.name }}</view>
</view>
<view v-if="categoryTree.length === 0" class="text-center text-[#888] py-[40rpx]">暂无数据</view>
</view>
<view class="px-[20rpx] py-2 flex justify-between sticky bottom-0 bg-white">
<tm-button skin="thin" block @click="emit('cancel')">取消</tm-button>
&nbsp;&nbsp;
<tm-button block :disabled="!selectedCategary" @click="onConfirm">确定</tm-button>
</view>
</template>
<script setup lang="ts">
import { ref } from "vue"
defineProps({
categoryTree: {
type: Array<any>,
default: () => [],
},
})
const emit = defineEmits(["confirm", "cancel"])
const selectedCategary = ref<any>({})
const onSelect = (item: any) => {
selectedCategary.value = item
}
function onConfirm() {
if (!selectedCategary.value) {
return uni.showToast({ title: "请选择分类", icon: "none" })
}
emit("confirm", { categoryId: selectedCategary.value.id, categoryName: selectedCategary.value.name })
}
</script>
<style scoped></style>