feat:适配
No files matched your search
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<crud-layout mainIsScroll bg="/static/page/blue-bg.png">
|
||||
<view class="h-40rpx"></view>
|
||||
<view class="card flex justify-between items-center mb-16rpx mx-20rpx p-32rpx" v-for="item in categoryList"
|
||||
:key="item.id">
|
||||
<image :src="filePath(item.picUrl)" class="size-100rpx mr-32rpx" />
|
||||
<view class="flex-1 flex-col">
|
||||
<view class="flex justify-between items-center">
|
||||
<text class="sub-text-30">{{ item.name }}</text>
|
||||
<text class="c-#0094ff! sub-text-24 px-16rpx bg-#0094ff22 rounded-2">{{ item.status == "0" ? "正常" :
|
||||
"禁用" }}</text>
|
||||
</view>
|
||||
<view class="flex justify-end items-center gap-16rpx mt-16rpx">
|
||||
<tm-button class="px-16rpx" size="s" :linear="['to right', '#62abff', '#0779FD99']"
|
||||
@click="editCategary(item)">编辑</tm-button>
|
||||
<tm-button class="px-16rpx" size="s" :linear="['to right', '#FFC328', '#E2000099']"
|
||||
@click="delCategary(item.id)">删除</tm-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="h-100rpx"></view>
|
||||
|
||||
<view class="fixed bottom-80rpx left-40rpx">
|
||||
<tm-button class="px-16rpx" size="m" :linear="['to right', '#0779FD', '#62abff']"
|
||||
@click="addCategary">新增分类</tm-button>
|
||||
</view>
|
||||
|
||||
<tm-drawer title="分类" :showClose="true" :showFooter="false" size="90%" v-model:show="categaryFormDrawer"
|
||||
:duration="100" zIndex="100" bgColor="#F5F5F5">
|
||||
<view class="card p-32rpx mb-16rpx">
|
||||
<view class="prefix_title mb-16rpx">基本信息</view>
|
||||
<tm-form @submit="saveCategary" v-model="form" :rules="[]" direction="horizontal">
|
||||
<tm-form-item name="name" label="分类名称">
|
||||
<tm-input v-model="form.name" type="text" :showClear="true" placeholder="请输入分类名称"></tm-input>
|
||||
</tm-form-item>
|
||||
<tm-form-item name="status" label="状态">
|
||||
<tm-radio-group v-model="form.status" align="flex-end">
|
||||
<tm-radio :value="item.value" :label="item.label" v-for="(item, index) in [
|
||||
{ label: '正常', value: '0' },
|
||||
{ label: '下架', value: '1' },
|
||||
]" :key="index" />
|
||||
</tm-radio-group>
|
||||
</tm-form-item>
|
||||
<view class="prefix_title mb-16rpx">父级分类</view>
|
||||
<view class="my-16rpx">
|
||||
<node-tree v-model="selectedNodeId" :node-data="categoryTree" />
|
||||
</view>
|
||||
<view class="prefix_title mb-16rpx">分类图</view>
|
||||
<tm-upload-photo v-model="form.picUrl" :maxFileSize="1024 * 1024 * 5"
|
||||
:url="'/api/admin/sys-file/upload'" :header="hooks.useDefaultHeader()" :column="1.6"
|
||||
img-height="360" :max-count="1" />
|
||||
<view class="sub-text-20 c-red! mb-16rpx">注:图片大小不超过5M</view>
|
||||
<tm-button form-type="submit" block>保 存</tm-button>
|
||||
</tm-form>
|
||||
</view>
|
||||
</tm-drawer>
|
||||
</crud-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { prosperityShopApi } from "@/sheep/api"
|
||||
import { onLoad } from "@dcloudio/uni-app"
|
||||
import { reactive, ref } from "vue"
|
||||
import NodeTree from "@/sheep/components/node-tree.vue"
|
||||
import { useCategaryManage } from "./use-categary-manage"
|
||||
import { hooks } from "@@/index"
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
|
||||
const categaryFormDrawer = ref(false)
|
||||
const form = reactive<any>({
|
||||
id: "",
|
||||
name: "",
|
||||
status: "0", // 状态(0正常,1下架)
|
||||
parentId: "",
|
||||
picUrl: [],
|
||||
})
|
||||
const categoryTree = ref([])
|
||||
const selectedNodeId = ref<string>("")
|
||||
|
||||
const { categoryList, getGoodsCategory, delCategary } = useCategaryManage()
|
||||
|
||||
onLoad(() => {
|
||||
getGoodsCategory()
|
||||
})
|
||||
|
||||
const getCategaryTree = async () => {
|
||||
const { code, data, msg } = await prosperityShopApi.getGoodsCategoryTree()
|
||||
if (code != 0) return uni.showToast({ title: msg || "商品分类获取失败", icon: "none" })
|
||||
categoryTree.value = data ?? []
|
||||
}
|
||||
|
||||
// 新增分类
|
||||
const addCategary = () => {
|
||||
getCategaryTree()
|
||||
Object.assign(form, {
|
||||
id: "",
|
||||
name: "",
|
||||
status: "0",
|
||||
parentId: "",
|
||||
picUrl: [],
|
||||
})
|
||||
selectedNodeId.value = ""
|
||||
categaryFormDrawer.value = true
|
||||
}
|
||||
|
||||
// 编辑分类
|
||||
const editCategary = (item) => {
|
||||
getCategaryTree()
|
||||
const picUrl = item.picUrl ? [{ path: filePath(item.picUrl), response: JSON.stringify({ data: { url: item.picUrl } }) }] : []
|
||||
Object.assign(form, { ...item, picUrl })
|
||||
selectedNodeId.value = item.parentId
|
||||
categaryFormDrawer.value = true
|
||||
}
|
||||
|
||||
// 提交保存
|
||||
const saveCategary = async () => {
|
||||
if (!form.name) return uni.showToast({ title: "请填写分类名称", icon: "none" })
|
||||
if (selectedNodeId.value === form.id) return uni.showToast({ title: "父级分类不能是自己", icon: "none" })
|
||||
try {
|
||||
const params = {
|
||||
...form,
|
||||
parentId: selectedNodeId.value,
|
||||
picUrl: form.picUrl?.length ? JSON.parse(form.picUrl[0].response).data.url : "",
|
||||
}
|
||||
uni.showLoading({ title: "保存中..." })
|
||||
const { code, msg } = await (form.id ? prosperityShopApi.editCategary(params) : prosperityShopApi.addCategary(params))
|
||||
if (code != 0) return uni.showToast({ title: msg || "保存失败", icon: "none" })
|
||||
uni.showToast({ title: "保存成功", icon: "none" })
|
||||
categaryFormDrawer.value = false
|
||||
await getGoodsCategory()
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<tm-drawer z-index="800" v-model:show="modelValue" showClose title="购物车" size="80%" bgColor="#ECF4F9" @close="doClose">
|
||||
<view v-if="totPrice > 0" class="flex-row card px-15rpx py-25rpx mb-20rpx" v-for="(v, i) in cartList" :key="i">
|
||||
<image class="size-160rpx rounded-12rpx mr-20rpx bg-#eeeeee" :src="filePath(v.productPic)" mode="aspectFill" />
|
||||
<view class="flex-1 overflow-hidden">
|
||||
<view class="sub-text-30 truncate">{{ v.productName }}</view>
|
||||
<view class="second-text-26 show-line">单位 {{ v.unit }}</view>
|
||||
<view class="flex-row justify-between items-center">
|
||||
<view class="second-text-30"> ¥{{ hooks.useMoney(v.productPrice) }} </view>
|
||||
<view class="second-text-30">x {{ v.productCount }}</view>
|
||||
</view>
|
||||
<view class="flex-row justify-between items-center">
|
||||
<view class="second-text-32 c-red!">¥{{ hooks.useMoney(v.totalPrice) }}</view>
|
||||
<view class="flex-row justify-between items-center">
|
||||
<view
|
||||
class="flex-row-center size-40rpx rounded-full border-1px border-solid border-color-#0094ff"
|
||||
@tap.stop="removeProductCount(v.productId, merchantId)"
|
||||
>
|
||||
<view class="iconfont icon-a--" />
|
||||
</view>
|
||||
<view class="sub-text-28 m-x-20rpx">{{ v.productCount }}</view>
|
||||
<view class="flex-row-center size-40rpx rounded-full bg-#0094ff" @tap.stop="addProductCount(v.productId, merchantId)">
|
||||
<view class="font-size-40rpx color-white iconfont icon-a-" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="flex-col justify-center items-center pt-300rpx">
|
||||
<image src="@/static/page/cart_empty.png" class="size-350rpx" />
|
||||
<text class="sub-text-32 font-bold">购物车空空如也</text>
|
||||
</view>
|
||||
|
||||
<view class="h-100rpx"></view>
|
||||
</tm-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
import useCartBuy from "../use-cart-buy"
|
||||
import { hooks } from "@/sheep"
|
||||
|
||||
const modelValue = defineModel("modelValue", { default: false })
|
||||
const props = defineProps({
|
||||
merchantId: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
const { totPrice, cartList, addProductCount, removeProductCount } = useCartBuy()
|
||||
|
||||
// 关闭
|
||||
const doClose = () => {
|
||||
modelValue.value = false
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<tm-drawer z-index="800" v-model:show="modelValue" showFooter showClose title="加入购物车" size="70%" bgColor="#ECF4F9"
|
||||
@close="doClose">
|
||||
<view class="flex-col card py-20rpx px-30rpx mb-20rpx">
|
||||
<view class="prefix_title mb-15rpx">商品信息</view>
|
||||
<view class="flex items-center">
|
||||
<image class="size-160rpx bg-#eeeeee rounded-1 mr-16rpx" :src="filePath(goodsInfo.coverImage)"
|
||||
mode="aspectFill"
|
||||
@tap.stop="goodsInfo.coverImage && usePreviewImage(filePath(goodsInfo.coverImage))" />
|
||||
<view class="flex-1 overflow-hidden">
|
||||
<view class="sub-text-32 truncate">{{ goodsInfo.title }}</view>
|
||||
<view class="second-text-32 c-red!">
|
||||
<text>¥{{ displayPrice(state.selectedSku.price, "") || 0 }}</text>
|
||||
<!-- <text class="m-l-20rpx second-text-28 line-through">¥{{ displayPrice(state.selectedSku.retailPrice, "") || 0 }}</text> -->
|
||||
</view>
|
||||
<view class="second-text-26">库存 {{ state.selectedSku.stock || 0 }}</view>
|
||||
<view class="second-text-26">
|
||||
已选择
|
||||
{{ `${state.selectedSku.skuName || "未选择"},购买数量 ${state.goodsCount}` }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 规格 -->
|
||||
<view class="flex-col card p-y-20rpx p-x-30rpx mb-20rpx">
|
||||
<view class="prefix_title mb-15rpx">规格</view>
|
||||
<view class="flex flex-wrap">
|
||||
<view
|
||||
class="text-center rounded-2 mr-32rpx mb-16rpx py-16rpx px-24rpx text-28 bg-#F2F2F2 c-black border border-solid border-#E5E5E5"
|
||||
:class="{ 'bg-#0094ff! c-white!': state.selectedSku.id === v.id && state.selectedSku.status === 0 }"
|
||||
v-for="v in goodsInfo.skuList" :key="v.id" @click="onSelectSku(v)">
|
||||
{{ v.skuName || "未知" }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 购买 数量 -->
|
||||
<view class="flex-col card p-y-20rpx p-x-30rpx mb-20rpx">
|
||||
<view class="prefix_title mb-15rpx">购买数量</view>
|
||||
<view class="flex items-center justify-between">
|
||||
<view class="second-text-28">库存 {{ state.selectedSku.stock }}</view>
|
||||
<view class="flex">
|
||||
<view class="flex-row-center size-40rpx rounded-full border-1px border-solid border-color-#0094ff"
|
||||
@tap.stop="handleNumRemove">
|
||||
<view class="iconfont icon-a--" />
|
||||
</view>
|
||||
<view class="sub-text-28 m-x-20rpx">{{ String(state.goodsCount).padStart(2, "0") }}</view>
|
||||
<view class="flex-row-center size-40rpx rounded-full bg-#0094ff" @tap.stop="handleNumAdd">
|
||||
<view class="font-size-40rpx color-white iconfont icon-a-" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="h-20rpx"></view>
|
||||
|
||||
<template #footer>
|
||||
<button class="mx-96rpx rounded-3 bg-#0094ff sub-text-30 py-16rpx c-white! active:bg-#0094ff99"
|
||||
:class="{ 'bg-#0094ff77!': Object.keys(state.selectedSku).length === 0 }" @tap="handleSkuAddToCart">
|
||||
加入购物车
|
||||
</button>
|
||||
</template>
|
||||
</tm-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, watch } from "vue"
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
import { usePreviewImage } from "@/sheep/hooks/use-img"
|
||||
import { isEmpty } from "radash"
|
||||
import { displayPrice } from "@/sheep/utils/format-money"
|
||||
|
||||
const modelValue = defineModel("modelValue", { default: false })
|
||||
const emit = defineEmits<{ (e: "onSkuAddToCart", val: object): void }>()
|
||||
const props = defineProps({
|
||||
goodsInfo: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
})
|
||||
|
||||
const state = reactive<any>({
|
||||
selectedSku: {},
|
||||
goodsCount: 1,
|
||||
})
|
||||
|
||||
watch(modelValue, (newValue) => {
|
||||
if (newValue && !isEmpty(props.goodsInfo)) {
|
||||
state.selectedSku = props.goodsInfo.skuList[0]
|
||||
}
|
||||
})
|
||||
|
||||
// 选择规格/取消选择规格
|
||||
const onSelectSku = (skuItem: any) => {
|
||||
if (state.selectedSku.id == skuItem.id) {
|
||||
state.selectedSku = {}
|
||||
return
|
||||
}
|
||||
state.selectedSku = skuItem
|
||||
}
|
||||
|
||||
// 加数量
|
||||
const handleNumAdd = () => {
|
||||
if (state.goodsCount >= state.selectedSku.stock) return uni.showToast({ title: "库存不足", icon: "none" })
|
||||
state.goodsCount += 1
|
||||
}
|
||||
|
||||
// 减数量
|
||||
const handleNumRemove = () => {
|
||||
if (state.goodsCount <= 1) return
|
||||
state.goodsCount -= 1
|
||||
}
|
||||
|
||||
// 点击sku加入到购物车
|
||||
const handleSkuAddToCart = async () => {
|
||||
if (!state.selectedSku.id) return uni.showToast({ title: "请选择包装单位", icon: "none" })
|
||||
|
||||
// 检查库存
|
||||
// try {
|
||||
// uni.showLoading({ title: "加载中", mask: true })
|
||||
// const { code }: any = await shopApi.checkSpuStock({ spuId: props.goodsInfo.id, incrCount: state.goodsCount })
|
||||
// if (code != 0) return uni.showToast({ title: "库存不足", icon: "none" })
|
||||
// } catch (e) {
|
||||
// console.error(e)
|
||||
// } finally {
|
||||
// uni.hideLoading()
|
||||
// }
|
||||
|
||||
// 检查库存
|
||||
if (props.goodsInfo.totalStock === 0 || Number(state.goodsCount) > Number(state.selectedSku.stock)) {
|
||||
return uni.showToast({ title: "库存不足", icon: "none" })
|
||||
}
|
||||
|
||||
emit("onSkuAddToCart", {
|
||||
spuId: props.goodsInfo.id,
|
||||
skuId: state.selectedSku.id,
|
||||
productCount: state.goodsCount,
|
||||
...state.selectedSku,
|
||||
})
|
||||
}
|
||||
|
||||
// 关闭
|
||||
const doClose = () => {
|
||||
modelValue.value = false
|
||||
state.selectedSku = {}
|
||||
state.goodsCount = 1
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<view class="flex-row items-center justify-between" @tap="doContainer">
|
||||
<view class="flex-row-center">
|
||||
<tm-badge bg-color="red" :label="totalCount" :offset="[8, 8]">
|
||||
<image class="size-100rpx" src="/static/icon/cart.png" />
|
||||
</tm-badge>
|
||||
<view class="flex-col m-l-30rpx">
|
||||
<view class="second-text-36 color-#f63a36!">¥ {{ hooks.useMoney(props.totPrice) }}</view>
|
||||
<view class="sub-text-28">共{{ props.totCount }}件</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="bg-#0094ff p-x-60rpx p-y-20rpx text-center rounded-40rpx sub-text-30 color-white!"
|
||||
@tap.stop="goSettlement">去结算</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { hooks } from "@@/index"
|
||||
import useCartBuy from "../use-cart-buy"
|
||||
|
||||
const emit = defineEmits(["submit", "doContainer"])
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
totPrice: number
|
||||
totCount: number
|
||||
}>(),
|
||||
{
|
||||
totPrice: 0,
|
||||
totCount: 0,
|
||||
},
|
||||
)
|
||||
|
||||
const { totalCount } = useCartBuy()
|
||||
|
||||
const goSettlement = () => emit("submit")
|
||||
|
||||
const doContainer = () => emit("doContainer")
|
||||
</script>
|
||||
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<crud-layout mainIsScroll bg="/static/page/blue-bg.png">
|
||||
<swiper class="w-full h-500rpx" circular :indicator-dots="true" indicator-active-color="#0779FD"
|
||||
indicator-color="#ffffff" :autoplay="true">
|
||||
<swiper-item v-for="(v, i) in goodsDetail.carouselImages?.split(',')" :key="i">
|
||||
<image class="w-full h-full" :src="filePath(v)" mode="aspectFill" />
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<view v-if="goodsDetail.id" class="card p-20rpx mt-10rpx mx-20rpx bg-gradient-to-b from-#0094ff33 to-#fff">
|
||||
<view v-if="goodsDetail.priceType == '2'" class="w-full flex justify-between items-center">
|
||||
<view class="flex items-center">
|
||||
<text class="second-text-34 line-through">¥</text>
|
||||
<text class="font-bold second-text-40 line-through mr-16rpx">
|
||||
{{ displayPrice(goodsDetail.originalPrice, "") }}
|
||||
</text>
|
||||
<text class="text-34rpx color-red!">¥</text>
|
||||
<text class="text-46rpx font-bold color-red!">{{ displayPrice(goodsDetail.spuPrice, "") }}</text>
|
||||
<text
|
||||
class="view-clip px-16rpx text-22rpx c-white bg-red ml-16rpx rounded-r-1 rounded-lt-1">到手价</text>
|
||||
</view>
|
||||
<view class="main-text-32 c-red! font-italic">
|
||||
<text>约省</text>
|
||||
<text class="text-44rpx mx-8rpx">{{ displayPrice(Number(goodsDetail.originalPrice) -
|
||||
Number(goodsDetail.spuPrice), "") }}</text>
|
||||
<text>元</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="w-full flex justify-start items-center">
|
||||
<text class="text-34rpx color-red!">¥</text>
|
||||
<text class="text-46rpx font-bold color-red!">
|
||||
{{ displayPrice(goodsDetail.skuList[0].price, "") }}
|
||||
</text>
|
||||
<text class="view-clip px-16rpx text-22rpx c-white bg-red ml-16rpx rounded-r-1 rounded-lt-1">到手价</text>
|
||||
</view>
|
||||
<view class="w-full sub-text-36 flex items-center mt-8rpx">
|
||||
<text class="c-#0094ff! sub-text-24 px-4rpx bg-#0094ff22 rounded-1 mr-16rpx whitespace-nowrap">
|
||||
{{ goodsDetail.status == "0" ? "在售" : "未售" }}
|
||||
</text>
|
||||
<text class="truncate">{{ goodsDetail.title }}</text>
|
||||
</view>
|
||||
<view class="second-text-26 mt-8rpx">售卖时间 {{ goodsDetail.saleStartTime }} ~ {{ goodsDetail.saleEndTime }}
|
||||
</view>
|
||||
<view class="second-text-26 truncate mt-8rpx flex justify-between items-center">
|
||||
<view>
|
||||
<!-- <text class="mr-20rpx">售卖单位 {{ goodsDetail.unit }}</text> -->
|
||||
<text class="mr-20rpx">剩余总库存 {{ goodsDetail.totalStock }}</text>
|
||||
</view>
|
||||
<view class="second-text-24 px-16rpx py-8rpx bg-#0094ff c-white! border-rd-8"
|
||||
@tap.stop="state.showSkuDialog = true">加入购物车</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="goodsDetail.priceType == '1'" class="mt-20rpx card p-20rpx mx-20rpx">
|
||||
<view class="prefix_title">规格</view>
|
||||
<view class="bg-#1879ff09 rounded-1 my-16rpx p-24rpx" v-for="(item, index) in goodsDetail.skuList"
|
||||
:key="index">
|
||||
<view class="sub-text-30">{{ item.skuName }}</view>
|
||||
<view class="second-text-28 mt-8rpx flex justify-between items-center">
|
||||
<text>¥{{ displayPrice(item.price) }}</text>
|
||||
<text>库存 {{ item.stock }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="mt-20rpx card p-20rpx mx-20rpx">
|
||||
<view class="prefix_title">详情</view>
|
||||
<view class="w-full mt-20rpx overflow-hidden text-26rpx!" v-html="description"></view>
|
||||
</view>
|
||||
<view class="h-30rpx" />
|
||||
|
||||
<template #main-bottom>
|
||||
<view class="z-200 p-x-30rpx p-y-30rpx bg-white">
|
||||
<product-total :totPrice="totPrice" :totCount="totalCount" @doContainer="doCart"
|
||||
@submit="goSettlement(state.mchId)" />
|
||||
</view>
|
||||
</template>
|
||||
</crud-layout>
|
||||
<product-sku v-model="state.showSkuDialog" :goodsInfo="state.goodsDetail" @onSkuAddToCart="onSkuAddToCart" />
|
||||
<product-cart v-model="state.showCartDialog" :merchantId="state.mchId" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onLoad } from "@dcloudio/uni-app"
|
||||
import { computed, reactive } from "vue"
|
||||
import { prosperityShopApi } from "@/sheep/api"
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
import { displayPrice } from "@/sheep/utils/format-money"
|
||||
import ProductTotal from "./components/product-total.vue"
|
||||
import ProductSku from "./components/product-sku.vue"
|
||||
import ProductCart from "./components/product-cart.vue"
|
||||
import useCartBuy from "./use-cart-buy"
|
||||
|
||||
const { totPrice, totalCount, skuAddToCart, getShopCart, goSettlement } = useCartBuy()
|
||||
|
||||
const state = reactive<any>({
|
||||
id: "",
|
||||
mchId: "",
|
||||
goodsDetail: {},
|
||||
showSkuDialog: false,
|
||||
showCartDialog: false,
|
||||
})
|
||||
const goodsDetail = computed(() => state.goodsDetail)
|
||||
const description = computed(() => {
|
||||
return goodsDetail.value.description?.replaceAll(`<img`, `<img style="width: 100%;"`)
|
||||
})
|
||||
|
||||
onLoad(async (option: any) => {
|
||||
const { id, mchId } = option
|
||||
if (!id || !mchId) return uni.showToast({ title: "参数错误", icon: "error" })
|
||||
state.id = option.id
|
||||
state.mchId = option.mchId
|
||||
uni.showLoading({ title: "加载中..." })
|
||||
await getGoodsDetail(id)
|
||||
await getShopCart(mchId)
|
||||
uni.hideLoading()
|
||||
})
|
||||
|
||||
// 获取商品详情
|
||||
const getGoodsDetail = async (id: string) => {
|
||||
const { code, data, msg } = await prosperityShopApi.getGoodsDetail(id)
|
||||
if (code != 0) return uni.showToast({ title: msg || "商品详情获取失败", icon: "none" })
|
||||
state.goodsDetail = data
|
||||
}
|
||||
|
||||
// 选择sku加入到购物车
|
||||
const onSkuAddToCart = (sku: any) => {
|
||||
skuAddToCart({ ...state.goodsDetail, ...sku, productId: sku.skuId }, state.mchId)
|
||||
state.showSkuDialog = false
|
||||
}
|
||||
|
||||
// 查看购物车
|
||||
const doCart = () => {
|
||||
state.showCartDialog = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep() {
|
||||
.layout_main>scroll-view {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.view-clip {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -18rpx;
|
||||
bottom: 0rpx;
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
background: radial-gradient(circle at 0 0, transparent 80%, red 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,340 @@
|
||||
<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="horizontal">
|
||||
<view class="card p-20rpx mb-20rpx">
|
||||
<view class="prefix_title mb-16rpx">商品基本信息</view>
|
||||
<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="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="productNature" label="商品性质">
|
||||
<tm-picker rang-key="title" rang-text="title" v-model="form.productNature"
|
||||
v-model:model-str="form.productNatureName" :list="[
|
||||
{ title: '普通商品', id: '1' },
|
||||
{ title: '电子称重商品', id: '2' },
|
||||
]">
|
||||
<tm-input v-model="form.productNature" placeholder="请选择商品性质" style="pointer-events: none"
|
||||
disabled />
|
||||
</tm-picker>
|
||||
</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="开售时间">
|
||||
<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="请选择开售时间" style="pointer-events: none"
|
||||
disabled align="right" color="transparent">
|
||||
<template v-slot:rightIcon>
|
||||
<tm-icon name="arrow-right-s-line"></tm-icon>
|
||||
</template>
|
||||
</tm-input>
|
||||
</tm-picker-date>
|
||||
</tm-form-item>
|
||||
<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="请选择停售时间" style="pointer-events: none"
|
||||
disabled align="right" color="transparent">
|
||||
<template v-slot:rightIcon>
|
||||
<tm-icon name="arrow-right-s-line"></tm-icon>
|
||||
</template>
|
||||
</tm-input>
|
||||
</tm-picker-date>
|
||||
</tm-form-item>
|
||||
<tm-form-item name="coverImage" label="商品主图">
|
||||
<tm-upload-photo v-model="form.coverImage" :maxFileSize="1024 * 1024 * 5"
|
||||
:url="'/api/admin/sys-file/upload'" :header="hooks.useDefaultHeader()" :column="1.2"
|
||||
img-height="360" :max-count="1" />
|
||||
</tm-form-item>
|
||||
<view class="sub-text-20 c-red! mb-16rpx">注:图片大小不超过5M</view>
|
||||
<!-- <tm-form-item name="carouselImages" label="轮播图">
|
||||
<tm-upload-photo
|
||||
v-model="form.carouselImages"
|
||||
:maxFileSize="1024 * 1024 * 5"
|
||||
:url="'/api/admin/sys-file/upload'"
|
||||
:header="hooks.useDefaultHeader()"
|
||||
:column="2"
|
||||
img-height="200"
|
||||
:max-count="5"
|
||||
/>
|
||||
</tm-form-item> -->
|
||||
<tm-form-item name="description" label="详情描述" direction="vertical" required="">
|
||||
<!-- <rich-text-input v-model="form.description" placeholder="点击编辑详情描述..." label="详情描述" /> -->
|
||||
<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-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 :max="10000" 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 :max="10000" 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-16rpx">商品SKU信息</view>
|
||||
<view v-for="(item, index) in form.skuList" :key="index">
|
||||
<view v-if="index !== 0" class="my-32rpx h-3rpx bg-#A0A0A044"></view>
|
||||
<view class="flex justify-between items-center mb-16rpx">
|
||||
<text>编号:{{ Number(index) + 1 }}</text>
|
||||
<tm-button v-if="index !== 0" class="px-16rpx" size="s" color="error" skin="thin"
|
||||
@click.stop="delSkuListItem(Number(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 :max="10000" 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 :max="10000" 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 :max="10000" 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="m" color="#E5F1FF" @click.stop="addSkuListItem()">添 加</tm-button>
|
||||
</view>
|
||||
|
||||
<tm-button form-type="submit" block>提 交</tm-button>
|
||||
</tm-form>
|
||||
|
||||
<tm-drawer title="请选择分类" :showClose="true" :showFooter="false" size="70%" v-model:show="showCategaryPicker">
|
||||
<shop-categary-picker v-model="form.categoryId" :categoryTree="categoryTree" @confirm="onCategaryConfirm"
|
||||
@cancel="showCategaryPicker = false" />
|
||||
</tm-drawer>
|
||||
|
||||
<view class="h-30rpx" />
|
||||
</crud-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, watch } 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"
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
import { displayPrice, storePrice } from "@/sheep/utils/format-money"
|
||||
|
||||
const form = reactive<any>({
|
||||
id: "",
|
||||
title: "",
|
||||
categoryId: "",
|
||||
categoryName: "",
|
||||
productNature: "",
|
||||
status: "0",
|
||||
saleStartTime: "",
|
||||
saleEndTime: "",
|
||||
coverImage: [],
|
||||
// carouselImages: [],
|
||||
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 categoryList = ref<any>([])
|
||||
|
||||
const rules: Record<string, TM.FORM_RULE[]> = {
|
||||
title: [{ required: true, message: "请输入商品标题" }],
|
||||
categoryId: [{ required: true, message: "请选择商品分类" }],
|
||||
productNature: [{ required: true, message: "请选择商品性质" }],
|
||||
status: [{ required: true, message: "请选择状态" }],
|
||||
saleStartTime: [{ required: true, message: "请选择售卖开始时间" }],
|
||||
saleEndTime: [{ required: true, message: "请选择售卖结束时间" }],
|
||||
coverImage: [{ required: true, message: "请上传商品主图" }],
|
||||
// carouselImages: [{ required: true, message: "请上传轮播图" }],
|
||||
}
|
||||
|
||||
onLoad(async (options: any) => {
|
||||
const { id = "" } = options
|
||||
if (!id) return
|
||||
await getGoodsCategory()
|
||||
await getGoodsDetail(id)
|
||||
})
|
||||
|
||||
const getGoodsDetail = async (id: string) => {
|
||||
uni.showLoading({ title: "加载中..." })
|
||||
const { code, data, msg } = await prosperityShopApi.getGoodsDetail(id)
|
||||
if (code != 0 || !data) return uni.showToast({ title: msg || "商品详情获取失败", icon: "none" })
|
||||
const skuList = data.skuList.map((item: any) => ({ ...item, status: item.status.toString(), price: displayPrice(item.price, "") }))
|
||||
const coverImage = [{ path: filePath(data.coverImage), response: JSON.stringify({ data: { url: data.coverImage } }) }]
|
||||
// const carouselImages = data.carouselImages?.split(",").map((url: string) => ({
|
||||
// path: filePath(url),
|
||||
// response: JSON.stringify({ data: { url } }),
|
||||
// }))
|
||||
const categoryName = categoryList.value.find((item: any) => item.id == data.categoryId)?.name
|
||||
const saleStartTime = data.saleStartTime
|
||||
const saleEndTime = data.saleEndTime
|
||||
_saleStartTimeStr.value = saleStartTime
|
||||
_saleEndTimeStr.value = saleEndTime
|
||||
|
||||
Object.assign(form, {
|
||||
...data,
|
||||
status: data.status.toString(),
|
||||
priceType: data.priceType.toString(),
|
||||
spuPrice: displayPrice(data.spuPrice, ""),
|
||||
originalPrice: displayPrice(data.originalPrice, ""),
|
||||
coverImage,
|
||||
// carouselImages,
|
||||
categoryName,
|
||||
saleStartTime,
|
||||
saleEndTime,
|
||||
skuList,
|
||||
})
|
||||
uni.hideLoading()
|
||||
}
|
||||
|
||||
const getGoodsCategory = async () => {
|
||||
const { code, data, msg } = await prosperityShopApi.getGoodsCategory()
|
||||
if (code != 0) return uni.showToast({ title: msg || "商品分类获取失败", icon: "none" })
|
||||
categoryList.value = data
|
||||
}
|
||||
|
||||
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 onsubmit = async (evt: TM.FORM_SUBMIT_RESULT) => {
|
||||
// console.log("校验结果:", evt)
|
||||
console.log(form.carouselImages)
|
||||
|
||||
const submitData = {
|
||||
...form,
|
||||
coverImage: JSON.parse(form.coverImage[0].response).data.url,
|
||||
// carouselImages: form.carouselImages.map((item: any) => JSON.parse(item.response).data.url).join(","),
|
||||
}
|
||||
console.log("提交的数据:", submitData)
|
||||
|
||||
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 params = {
|
||||
...form,
|
||||
spuPrice: storePrice(form.spuPrice),
|
||||
originalPrice: storePrice(form.originalPrice),
|
||||
skuList: form.skuList.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
price: storePrice(item.price),
|
||||
}
|
||||
}),
|
||||
coverImage: JSON.parse(form.coverImage[0].response).data.url,
|
||||
// carouselImages: form.carouselImages.map((item: any) => JSON.parse(item.response).data.url).join(","),
|
||||
}
|
||||
uni.showLoading({ title: "提交中..." })
|
||||
const { code, msg } = await (form.id ? prosperityShopApi.editGoods(params) : prosperityShopApi.addGoods(params))
|
||||
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" })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<crud-layout mainIsScroll bg="/static/page/blue-bg.png">
|
||||
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="pageHook.list" :default-page-size="10"
|
||||
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
|
||||
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
|
||||
<template #top>
|
||||
<tm-input class="mt-16rpx" 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-8rpx px-24rpx py-26rpx text-30rpx!" size="xs" @click="search">搜
|
||||
索</tm-button>
|
||||
</template>
|
||||
</tm-input>
|
||||
<scroll-view scroll-x>
|
||||
<view class="w-full flex my-16rpx">
|
||||
<view
|
||||
class="w-300rpx mr-16rpx sub-text-28 py-8rpx px-16rpx rounded-1 bg-#fff border border-solid border-transparent whitespace-nowrap"
|
||||
:class="{ 'bg-#0779FD22! c-#0779FD! border-#0779FD33!': state.params.categoryId === v.id }"
|
||||
v-for="(v, i) in state.category" :key="i" @tap="doClassify(v.id)">
|
||||
{{ v.name }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<view v-for="v in pageHook.list" :key="v.id" class="card mb-16rpx px-32rpx py-16rpx">
|
||||
<view class="flex">
|
||||
<image :src="filePath(v.coverImage)" class="size-180rpx rounded-12rpx bg-#eeeeee" mode="aspectFill"
|
||||
@click.stop="hooks.usePreviewImage(filePath(v.coverImage))" />
|
||||
<view class="flex-1 ml-16rpx overflow-hidden">
|
||||
<view class="truncate sub-text-32">{{ v.title }}</view>
|
||||
<view class="sub-text-24 my-6rpx">
|
||||
<text class="c-#0094ff! px-16rpx bg-#0094ff22 rounded-2 mr-16rpx">
|
||||
{{state.category.find((item) => item.id == v.categoryId)?.name || "未知分类"}}
|
||||
</text>
|
||||
<text class="c-#0094ff! px-16rpx bg-#0094ff22 rounded-2">{{ v.status == "0" ? "在售" : "未售"
|
||||
}}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="second-text-26 mr-16rpx">单位 {{ v.unit }}</text>
|
||||
<text class="second-text-26">库存 {{ v.totalStock }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<template v-if="v.priceType == '2'">
|
||||
<text class="second-text-32 line-through mr-8rpx">
|
||||
<text class="text-30rpx">¥</text>
|
||||
<text class="font-bold">{{ displayPrice(v.originalPrice, "") }}</text>
|
||||
</text>
|
||||
<text class="color-red">
|
||||
<text class="text-30rpx">¥</text>
|
||||
<text class="text-40rpx font-bold">{{ displayPrice(v.spuPrice, "").split(".")[0]
|
||||
}}.</text>
|
||||
<text class="text-32rpx font-bold">{{ displayPrice(v.spuPrice, "").split(".")[1]
|
||||
}}</text>
|
||||
</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="color-red">
|
||||
<text class="text-30rpx">¥</text>
|
||||
<text class="text-40rpx font-bold">{{ displayPrice(v.minPrice, "").split(".")[0]
|
||||
}}.</text>
|
||||
<text class="text-32rpx font-bold">{{ displayPrice(v.minPrice, "").split(".")[1]
|
||||
}}</text>
|
||||
</text>
|
||||
</template>
|
||||
<text
|
||||
class="view-clip px-8rpx text-18rpx c-white bg-red ml-8rpx rounded-r-1 rounded-lt-1">到手价</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="my-16rpx h-1rpx bg-#A0A0A044"></view>
|
||||
<view class="flex gap-16rpx justify-end">
|
||||
<tm-button class="px-16rpx" size="s" :linear="['to right', '#FFC328', '#E2000099']"
|
||||
@click.stop="deleteGoods(v.id)">
|
||||
删除商品
|
||||
</tm-button>
|
||||
<tm-button class="px-16rpx" size="s" :linear="['to right', '#62abff', '#0779FD99']"
|
||||
@click.stop="editGoods(v.id)">
|
||||
编辑商品
|
||||
</tm-button>
|
||||
<tm-button class="px-16rpx" size="s" :linear="['to right', '#00B960', '#00552C99']"
|
||||
@click.stop="doGoodsDetail(v.id, v.merchantId)">
|
||||
查看详情
|
||||
</tm-button>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
<view class="fixed bottom-80rpx left-40rpx flex-col gap-16rpx">
|
||||
<tm-button class="px-16rpx" size="m" :linear="['to right', '#0779FD', '#62abff']"
|
||||
@click="addGoods">新增商品</tm-button>
|
||||
<tm-button class="px-16rpx" size="m" :linear="['to right', '#0779FD', '#62abff']"
|
||||
@click="categaryManage">分类管理</tm-button>
|
||||
</view>
|
||||
</crud-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from "vue"
|
||||
import { prosperityShopApi } from "@/sheep/api"
|
||||
import { hooks } from "@/sheep"
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app"
|
||||
import { displayPrice } from "@/sheep/utils/format-money"
|
||||
import { useGoodsManage } from "./use-goods-manage"
|
||||
|
||||
const state = reactive<any>({
|
||||
params: {
|
||||
keyword: "",
|
||||
categoryId: "",
|
||||
},
|
||||
category: [],
|
||||
curCategoryIndex: 0,
|
||||
})
|
||||
|
||||
const pageRef = ref<any>(null)
|
||||
const pageHook = hooks.usePage(pageRef, prosperityShopApi.getManageGoodsList, null, "data.records")
|
||||
|
||||
const query = (page: number, pagesize: number) => {
|
||||
pageHook.startPage(page, pagesize, false, state.params)
|
||||
}
|
||||
|
||||
const { addGoods, editGoods, deleteGoods, doGoodsDetail, categaryManage } = useGoodsManage(pageRef)
|
||||
|
||||
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" })
|
||||
state.category = data ?? []
|
||||
state.category.unshift({ id: "", name: "全部分类" })
|
||||
}
|
||||
|
||||
// 切换分类
|
||||
const doClassify = async (categoryId: string) => {
|
||||
if (state.params.categoryId == categoryId) return
|
||||
state.params.categoryId = categoryId
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
}
|
||||
|
||||
// 点击搜索
|
||||
const search = () => {
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
}
|
||||
|
||||
const inputClear = () => {
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.view-clip {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -18rpx;
|
||||
bottom: 0rpx;
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
background: radial-gradient(circle at 0 0, transparent 80%, red 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<crud-layout mainIsScroll bg="/static/page/blue-bg.png">
|
||||
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="pageHook.list" :default-page-size="10"
|
||||
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
|
||||
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
|
||||
<template #top>
|
||||
<view class="w-full my-16rpx">
|
||||
<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">
|
||||
<template v-slot:rightIcon>
|
||||
<tm-button class="mr-8rpx px-24rpx py-26rpx text-30rpx!" size="xs" @click="search">搜
|
||||
索</tm-button>
|
||||
</template>
|
||||
</tm-input>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view v-for="v in pageHook.list" :key="v.id" class="card flex mx-20rpx mb-16rpx p-16rpx"
|
||||
@click="doGoodsDetail(v.id)">
|
||||
<image :src="filePath(v.coverImage)" class="size-160rpx rounded-12rpx bg-#eeeeee" mode="aspectFill" />
|
||||
<view class="flex-1 ml-16rpx overflow-hidden">
|
||||
<view class="truncate sub-text-30">{{ v.title }}</view>
|
||||
<view>
|
||||
<text class="c-#0094ff! sub-text-24 px-16rpx bg-#0094ff22 rounded-2">{{ v.status == "0" ? "在售" :
|
||||
"未售" }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="second-text-26 mr-16rpx">单位 {{ v.unit }}</text>
|
||||
<text class="second-text-26">库存 {{ v.totalStock }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<template v-if="v.priceType == '2'">
|
||||
<text class="second-text-28 line-through mr-8rpx">
|
||||
<text class="text-26rpx">¥</text>
|
||||
<text class="font-bold">{{ displayPrice(v.originalPrice, "") }}</text>
|
||||
</text>
|
||||
<text class="color-red">
|
||||
<text class="text-26rpx">¥</text>
|
||||
<text class="text-32rpx font-bold">{{ displayPrice(v.spuPrice, "").split(".")[0]
|
||||
}}.</text>
|
||||
<text class="text-30rpx font-bold">{{ displayPrice(v.spuPrice, "").split(".")[1]
|
||||
}}</text>
|
||||
</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="color-red">
|
||||
<text class="text-26rpx">¥</text>
|
||||
<text class="text-32rpx font-bold">
|
||||
{{ displayPrice(v.minPrice, "").split(".")[0] }}.
|
||||
</text>
|
||||
<text class="text-30rpx font-bold">
|
||||
{{ displayPrice(v.minPrice, "").split(".")[1] }}
|
||||
</text>
|
||||
</text>
|
||||
</template>
|
||||
<text
|
||||
class="view-clip px-8rpx text-18rpx c-white bg-red ml-8rpx rounded-r-1 rounded-lt-1">到手价</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
</crud-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { hooks } from "@/sheep"
|
||||
import { prosperityShopApi } from "@/sheep/api"
|
||||
import { reactive, ref } from "vue"
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
import { onLoad } from "@dcloudio/uni-app"
|
||||
import { displayPrice } from "@/sheep/utils/format-money"
|
||||
|
||||
const state = reactive<any>({
|
||||
params: {
|
||||
keyword: "",
|
||||
merchantId: "",
|
||||
},
|
||||
})
|
||||
const pageRef = ref<any>(null)
|
||||
const pageHook = hooks.usePage(pageRef, prosperityShopApi.getGoodsList, null, "data.records")
|
||||
|
||||
const query = (page: number, pagesize: number) => {
|
||||
pageHook.startPage(page, pagesize, false, state.params)
|
||||
}
|
||||
|
||||
onLoad((options: any) => {
|
||||
const { mchId } = options
|
||||
if (!mchId) return uni.showToast({ title: "参数错误", icon: "none" })
|
||||
state.params.merchantId = mchId
|
||||
})
|
||||
|
||||
// 点击搜索
|
||||
const search = () => {
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
}
|
||||
|
||||
const doGoodsDetail = (id: string) => {
|
||||
if (!id) return uni.showToast({ title: "商品id不存在", icon: "none" })
|
||||
uni.navigateTo({
|
||||
url: `/pages/prosperity-shop/goods-detail?id=${id}&mchId=${state.params.merchantId}`,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.view-clip {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -18rpx;
|
||||
bottom: 0rpx;
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
background: radial-gradient(circle at 0 0, transparent 80%, red 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<crud-layout title="结算" mainIsScroll bg="/static/page/blue-bg.png">
|
||||
<view class="h-20rpx" />
|
||||
<view class="bg-#FFA61Ecc color-#fff px-16rpx py-8rpx text-28rpx rounded-2">温馨提示:当天的订单预计明天开始配送</view>
|
||||
|
||||
<tm-sticky-header>
|
||||
<view class="h-20rpx" />
|
||||
<view class="card p-20rpx">
|
||||
<view class="prefix_title text-36rpx mb-16rpx">自提点</view>
|
||||
<view class="flex-row items-center justify-between" @click="onPickupPoint">
|
||||
<view v-if="curPickup.id" class="sub-text-30 mb-16rpx flex items-center">
|
||||
<image class="size-80rpx rounded-1 mr-16rpx" :src="filePath(curPickup.logo)" />
|
||||
<view>
|
||||
<view>{{ curPickup.name }}</view>
|
||||
<view class="sub-text-26">{{ curPickup.phone }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="sub-text-30 mb-16rpx">请选择自提点</view>
|
||||
<view class="flex-row items-center second-text-28 my-24rpx">
|
||||
<text>请选择自提点</text>
|
||||
<text class="ml-10rpx color-#b1b1b1 iconfont icon-xiangxiajiantouxiao" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="c-#0089ff text-24rpx">配送订单较多时,配送时间可能波动</view>
|
||||
</view>
|
||||
<view class="h-20rpx" />
|
||||
</tm-sticky-header>
|
||||
|
||||
<view class="card p-20rpx mb-20rpx">
|
||||
<view class="prefix_title text-36rpx mb-16rpx">我的购买</view>
|
||||
<view v-if="totalCount > 0" class="flex-row mb-20rpx" v-for="(v, i) in cartList" :key="i">
|
||||
<image class="size-140rpx rounded-12rpx mr-20rpx bg-#eeeeee" :src="filePath(v.productPic)" mode="aspectFill" />
|
||||
<view class="flex-1 overflow-hidden">
|
||||
<view class="sub-text-30 truncate">{{ v.productName }}</view>
|
||||
<view class="flex justify-between items-center">
|
||||
<view class="flex-col">
|
||||
<view class="second-text-30">¥{{ hooks.useMoney(v.productPrice) }}</view>
|
||||
<view class="second-text-30">x {{ v.productCount }}</view>
|
||||
</view>
|
||||
<view class="second-text-32 c-red!">¥{{ hooks.useMoney(v.totalPrice) }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="text-center second-text-28 py-320rpx">您尚未选择任何商品</view>
|
||||
</view>
|
||||
<view class="h-20rpx" />
|
||||
|
||||
<template #main-bottom>
|
||||
<view class="flex justify-end items-center p-32rpx">
|
||||
<view class="flex-col mr-32rpx">
|
||||
<view class="second-text-36 color-#f63a36!">¥ {{ hooks.useMoney(totPrice) }}</view>
|
||||
<view class="sub-text-28">共{{ totalCount }}件</view>
|
||||
</view>
|
||||
<view class="bg-#0094ff px-60rpx py-20rpx text-center rounded-full sub-text-32 color-white!" @tap.stop="submitOrder">提交订单</view>
|
||||
</view>
|
||||
</template>
|
||||
</crud-layout>
|
||||
|
||||
<tm-drawer v-model:show="state.showDrawer" title="选择自提点" :showClose="true" :showFooter="false" size="86%" bgColor="#F3FAFF">
|
||||
<z-paging
|
||||
ref="pageRef"
|
||||
height="100%"
|
||||
v-model="pageHook.list"
|
||||
:default-page-size="10"
|
||||
safe-area-inset-bottom
|
||||
:loading-more-enabled="true"
|
||||
@query="query"
|
||||
:auto-clean-list-when-reload="false"
|
||||
:auto-scroll-to-top-when-reload="false"
|
||||
@scroll="pageHook.scroll"
|
||||
:show-loading-more-no-more-view="false"
|
||||
>
|
||||
<view class="h-100rpx"></view>
|
||||
<tm-radio-group v-model="state.curPickupId" direction="column" @change="radioChange">
|
||||
<view class="card p-16rpx">
|
||||
<tm-radio v-for="(v, i) in pageHook.list" :key="i" class="w-full h-full px-16rpx" :value="v.id">
|
||||
<template v-slot:label="checked">
|
||||
<view class="flex items-center mb-8rpx">
|
||||
<image class="size-140rpx rounded-12rpx mr-16rpx" :src="filePath(v.logo)" mode="aspectFill" />
|
||||
<view class="flex-1 overflow-hidden">
|
||||
<view class="sub-text-30 truncate">{{ v.name }}</view>
|
||||
<view class="sub-text-26">{{ v.phone }}</view>
|
||||
<view class="sub-text-24" v-for="(item, index) in JSON.parse(v.businessHoursJson)" :key="index">
|
||||
<text>{{ item.title }}:</text>
|
||||
<text>{{ item.startTime }} ~ </text>
|
||||
<text>{{ item.endTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<tm-text class="mx-20rpx mb-8rpx">地址:{{ v.detailAddress || "暂无" }}</tm-text>
|
||||
<tm-collapse v-if="v.introduction" @click.stop>
|
||||
<tm-collapse-item :name="i" title="简介" titleFontSize="28" activeColor="#000" titleHeight="40" :showBottomLine="false">
|
||||
<tm-text>{{ v.introduction }}</tm-text>
|
||||
<template #right>
|
||||
<text class="second-text-24">展开</text>
|
||||
</template>
|
||||
</tm-collapse-item>
|
||||
</tm-collapse>
|
||||
<view class="my-16rpx h-1rpx bg-#A0A0A066"></view>
|
||||
</template>
|
||||
</tm-radio>
|
||||
</view>
|
||||
</tm-radio-group>
|
||||
</z-paging>
|
||||
</tm-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from "vue"
|
||||
import { hooks } from "@/sheep"
|
||||
import useCartBuy from "./use-cart-buy"
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
import { onLoad } from "@dcloudio/uni-app"
|
||||
import { walletApi } from "@/sheep/api"
|
||||
|
||||
const { cartList, totPrice, totalCount, getShopCart, doBuy } = useCartBuy()
|
||||
|
||||
const state = reactive<any>({
|
||||
mchId: "",
|
||||
showDrawer: false,
|
||||
curPickupId: "",
|
||||
})
|
||||
const curPickup = computed(() => {
|
||||
return pageHook.list.find((item: any) => item.id === state.curPickupId) || {}
|
||||
})
|
||||
const pageRef = ref<any>(null)
|
||||
const pageHook = hooks.usePage(pageRef, walletApi.pickUpPage, null, "data.records")
|
||||
const query = (page: number, pagesize: number) => {
|
||||
pageHook.startPage(page, pagesize, false, { mchId: state.mchId })
|
||||
}
|
||||
|
||||
onLoad((option: any) => {
|
||||
const { mchId } = option
|
||||
if (!mchId) return uni.showToast({ title: "参数错误", icon: "error" })
|
||||
state.mchId = option.mchId
|
||||
getShopCart(mchId)
|
||||
})
|
||||
|
||||
// 选择自提点
|
||||
const onPickupPoint = () => {
|
||||
state.showDrawer = true
|
||||
}
|
||||
const radioChange = () => {
|
||||
state.showDrawer = false
|
||||
}
|
||||
|
||||
const submitOrder = () => {
|
||||
if (!curPickup.value.id) return uni.showToast({ title: "请选择自提点", icon: "none" })
|
||||
doBuy(curPickup.value.id)
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,299 @@
|
||||
<template>
|
||||
<crud-layout mainIsScroll bg="/static/page/blue-bg.png" @scroll="onScroll">
|
||||
<view class="w-full h-400rpx relative">
|
||||
<image :src="filePath(mchDetail.images)" class="w-full h-full bg-#eeeeee" mode="aspectFill" />
|
||||
<view class="absolute w-full h-full top-0 left-0" :style="filterStyle"></view>
|
||||
</view>
|
||||
<view
|
||||
class="fixed right-160rpx h-72rpx bg-#ECF4F9cc px-20rpx top-160rpx flex items-center flex-nowrap overflow-hidden rounded-20rpx"
|
||||
:style="searchStyle" @click="goSearch">
|
||||
<image class="w-36rpx h-36rpx" src="@/static/page/shop-goods-search.png" />
|
||||
<text v-if="scrollDetail.scrollTop > 30"
|
||||
class="bg-#eeeeee44 whitespace-nowrap overflow-hidden second-text-26 ml-16rpx">搜索商品</text>
|
||||
</view>
|
||||
|
||||
<view v-if="mchDetail.id" class="card p-20rpx mt-10rpx mx-20rpx bg-gradient-to-b from-#0094ff33 to-#fff">
|
||||
<view class="w-full sub-text-32 truncate">{{ mchDetail.name }}</view>
|
||||
<text class="sub-text-24 c-#0094ff! px-16rpx bg-#0094ff22 rounded-16rpx mr-32rpx">营业</text>
|
||||
<text class="second-text-24 truncate mt-8rpx">营业人{{ mchDetail.contactName }} · {{ mchDetail.contactPhone ||
|
||||
"无"
|
||||
}}</text>
|
||||
<view class="second-text-26 mt-8rpx">
|
||||
<image class="w-24rpx h-24rpx mr-16rpx" src="@/static/page/address.svg" />
|
||||
<text>{{ mchDetail.address || "暂无" }}</text>
|
||||
</view>
|
||||
<!-- <marquee class="second-text-24 mt-8rpx">{{ mchDetail.remark }}</marquee> -->
|
||||
</view>
|
||||
|
||||
<view class="flex mt-16rpx">
|
||||
<scroll-view scroll-y class="menu w-200rpx mb-20rpx bg-#f6f6f6 pl-20rpx"
|
||||
style="height: calc(100vh - 300rpx)">
|
||||
<view class="menu_item flex-row-center" v-for="(v, i) in state.category" :key="v.id"
|
||||
:class="[{ menu_item_active: i == state.curCategoryIndex }]" @tap="doCategory(i)">
|
||||
<view class="menu_item_title show-line">{{ v.name }}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<scroll-view v-if="state.goodsList.length > 0" scroll-y class="flex-1 overflow-hidden pb-20rpx flex"
|
||||
style="height: calc(100vh - 420rpx)">
|
||||
<view v-for="v in state.goodsList" :key="v.id" class="card flex mx-15rpx mb-16rpx p-8rpx"
|
||||
@click="doGoodsDetail(v.id)">
|
||||
<image :src="filePath(v.coverImage)" class="size-170rpx rounded-12rpx bg-#eeeeee"
|
||||
mode="aspectFill" />
|
||||
<view class="flex-1 ml-16rpx overflow-hidden">
|
||||
<view class="truncate sub-text-30">{{ v.title }}</view>
|
||||
<view>
|
||||
<text class="c-#0094ff! sub-text-24 px-16rpx bg-#0094ff22 rounded-2">
|
||||
{{ v.status == "0" ? "在售" : "已停售" }}
|
||||
</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="second-text-26">库存 {{ v.totalStock }}</text>
|
||||
<!-- <text class="second-text-26 mr-16rpx">单位 {{ v.unit }}</text> -->
|
||||
</view>
|
||||
<view class="flex items-center justify-between">
|
||||
<view class="flex items-center">
|
||||
<template v-if="v.priceType == '2'">
|
||||
<text class="second-text-28 line-through mr-8rpx">
|
||||
<text class="text-26rpx">¥</text>
|
||||
<text>{{ displayPrice(v.originalPrice, "") }}</text>
|
||||
</text>
|
||||
<text class="color-red">
|
||||
<text class="text-26rpx">¥</text>
|
||||
<text class="text-32rpx">{{ displayPrice(v.spuPrice, "").split(".")[0]
|
||||
}}.</text>
|
||||
<text class="text-30rpx">{{ displayPrice(v.spuPrice, "").split(".")[1] }}</text>
|
||||
</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="color-red">
|
||||
<text class="text-26rpx">¥</text>
|
||||
<text class="text-32rpx">{{ displayPrice(v.minPrice, "").split(".")[0]
|
||||
}}.</text>
|
||||
<text class="text-30rpx">{{ displayPrice(v.minPrice, "").split(".")[1] }}</text>
|
||||
</text>
|
||||
</template>
|
||||
<text
|
||||
class="view-clip px-8rpx text-20rpx c-white bg-red ml-8rpx rounded-r-8rpx rounded-lt-8rpx">到手价</text>
|
||||
</view>
|
||||
<view class="second-text-24 px-16rpx py-2rpx bg-#0094ff c-white! border-rd-8"
|
||||
@tap.stop="doSelectSku(v.id)">选规格</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view v-else class="mx-auto mt-80rpx second-text-28">暂无商品</view>
|
||||
</view>
|
||||
|
||||
<template #main-bottom>
|
||||
<view class="z-200 p-x-30rpx p-y-30rpx bg-white">
|
||||
<product-total :totPrice="totPrice" :totCount="totalCount" @doContainer="doCart"
|
||||
@submit="goSettlement(state.mchId)" />
|
||||
</view>
|
||||
</template>
|
||||
</crud-layout>
|
||||
<product-sku v-model="state.showSkuDialog" :goodsInfo="state.goodsDetail" @onSkuAddToCart="onSkuAddToCart" />
|
||||
<product-cart v-model="state.showCartDialog" :merchantId="state.mchId" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive } from "vue"
|
||||
import { onLoad } from "@dcloudio/uni-app"
|
||||
import { prosperityShopApi } from "@/sheep/api"
|
||||
import { filePath } from "@/sheep/utils/file"
|
||||
import treeToList from "@/sheep/utils/tree-to-list"
|
||||
import { displayPrice } from "@/sheep/utils/format-money"
|
||||
import useShopDetailScroll from "./use-shop-detail-scroll"
|
||||
import useCartBuy from "./use-cart-buy"
|
||||
import ProductTotal from "./components/product-total.vue"
|
||||
import ProductSku from "./components/product-sku.vue"
|
||||
import ProductCart from "./components/product-cart.vue"
|
||||
import { isEmpty } from "radash"
|
||||
|
||||
const state = reactive<any>({
|
||||
mchId: "",
|
||||
mchDetail: {},
|
||||
category: [],
|
||||
curCategoryIndex: 0,
|
||||
goodsList: [],
|
||||
goodsDetail: {},
|
||||
showSkuDialog: false,
|
||||
showCartDialog: false,
|
||||
})
|
||||
const mchDetail = computed(() => state.mchDetail)
|
||||
|
||||
const { scrollDetail, onScroll, filterStyle, searchStyle } = useShopDetailScroll()
|
||||
const { totPrice, totalCount, skuAddToCart, getShopCart, goSettlement } = useCartBuy()
|
||||
|
||||
onLoad(async (option: any) => {
|
||||
const { id } = option
|
||||
if (!id) return uni.showToast({ title: "参数错误", icon: "error" })
|
||||
state.mchId = id
|
||||
uni.showLoading({ title: "加载中..." })
|
||||
await getMchDetail(id)
|
||||
await getGoodsCategoryTree(id)
|
||||
await getGoodsList()
|
||||
await getShopCart(id)
|
||||
uni.hideLoading()
|
||||
})
|
||||
|
||||
// 获取商户详情
|
||||
const getMchDetail = async (id: string) => {
|
||||
const { code, data, msg } = await prosperityShopApi.getShopDetail(id)
|
||||
if (code != 0) return uni.showToast({ title: msg || "超市详情获取失败", icon: "none" })
|
||||
state.mchDetail = data
|
||||
}
|
||||
|
||||
// 获取商品分类
|
||||
const getGoodsCategoryTree = async (id: string) => {
|
||||
const { code, data, msg } = await prosperityShopApi.getGoodsCategoryTree({ merchantId: id })
|
||||
if (code != 0) return uni.showToast({ title: msg || "商品分类获取失败", icon: "none" })
|
||||
state.category = treeToList(data) ?? []
|
||||
}
|
||||
|
||||
// 获取商品列表
|
||||
const getGoodsList = async () => {
|
||||
if (isEmpty(state.category)) return
|
||||
const params = {
|
||||
current: 1,
|
||||
size: 99999,
|
||||
merchantId: state.mchId,
|
||||
categoryId: state.category[state.curCategoryIndex].id,
|
||||
}
|
||||
const { code, data, msg } = await prosperityShopApi.getGoodsList(params)
|
||||
if (code != 0) return uni.showToast({ title: msg || "商品列表获取失败", icon: "none" })
|
||||
state.goodsList = data?.records ?? []
|
||||
}
|
||||
|
||||
// 切换分类
|
||||
const doCategory = async (index: number | string) => {
|
||||
const targetIndex = Number(index)
|
||||
if (state.curCategoryIndex == targetIndex) return
|
||||
state.curCategoryIndex = targetIndex
|
||||
uni.showLoading({ title: "加载中..." })
|
||||
await getGoodsList()
|
||||
uni.hideLoading()
|
||||
}
|
||||
|
||||
// 去查看商品详情
|
||||
const doGoodsDetail = (id: string) => {
|
||||
if (!id || !state.mchId) return uni.showToast({ title: "id错误", icon: "none" })
|
||||
uni.navigateTo({
|
||||
url: `/pages/prosperity-shop/goods-detail?id=${id}&mchId=${state.mchId}`,
|
||||
})
|
||||
}
|
||||
|
||||
// 去搜索商品
|
||||
const goSearch = () => {
|
||||
if (!state.mchDetail.id) return uni.showToast({ title: "超市id不存在", icon: "none" })
|
||||
uni.navigateTo({ url: `/pages/prosperity-shop/goods-search?mchId=${state.mchId}` })
|
||||
}
|
||||
|
||||
// 选规格
|
||||
const doSelectSku = async (id: string) => {
|
||||
if (!id) return uni.showToast({ title: "商品id不存在", icon: "none" })
|
||||
uni.showLoading({ title: "加载中..." })
|
||||
const { code, data, msg } = await prosperityShopApi.getGoodsDetail(id)
|
||||
if (code != 0) return uni.showToast({ title: msg || "商品详情获取失败", icon: "none" })
|
||||
uni.hideLoading()
|
||||
state.goodsDetail = data
|
||||
state.showSkuDialog = true
|
||||
}
|
||||
|
||||
// 选择sku加入到购物车
|
||||
const onSkuAddToCart = (sku: any) => {
|
||||
skuAddToCart({ ...state.goodsDetail, ...sku, productId: sku.skuId }, state.mchId)
|
||||
state.showSkuDialog = false
|
||||
}
|
||||
|
||||
// 查看购物车
|
||||
const doCart = () => {
|
||||
state.showCartDialog = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep() {
|
||||
.layout_main>scroll-view {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
&_item {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
position: relative;
|
||||
transition: all linear 0.2s;
|
||||
|
||||
&_title {
|
||||
width: 100%;
|
||||
line-height: 32rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
margin-left: 28rpx;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
width: 64rpx;
|
||||
height: 12rpx;
|
||||
background: linear-gradient(90deg, #0094ff, #e9f5fe) !important;
|
||||
position: absolute;
|
||||
left: -64rpx;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
transition: all linear 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
&.menu_item_active {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx 0 0 20rpx;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: -20rpx;
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
background: radial-gradient(circle at 0 100%, transparent 20rpx, #fff 0);
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -20rpx;
|
||||
right: 0;
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
background: radial-gradient(circle at 0% 0%, transparent 20rpx, #fff 0);
|
||||
}
|
||||
|
||||
.menu_item_title {
|
||||
font-weight: 600;
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.view-clip {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -18rpx;
|
||||
bottom: 0rpx;
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
background: radial-gradient(circle at 0 0, transparent 80%, red 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,95 @@
|
||||
import { router } from "@/sheep"
|
||||
import { orderApi } from "@/sheep/api"
|
||||
import { useProsperityCartStore } from "@/sheep/stores/prosperity-cart"
|
||||
import { isEmpty } from "radash"
|
||||
import { computed } from "vue"
|
||||
|
||||
export default function useCartBuy() {
|
||||
const cartStore = useProsperityCartStore()
|
||||
|
||||
// 购物车商品总金额
|
||||
const totPrice = computed(() => {
|
||||
return cartStore.totPrice
|
||||
})
|
||||
|
||||
// 购物车商品总数量
|
||||
const totalCount = computed(() => {
|
||||
return cartStore.totalCount
|
||||
})
|
||||
|
||||
// 购物车商品列表
|
||||
const cartList = computed(() => {
|
||||
return cartStore.cartList
|
||||
})
|
||||
|
||||
// 选择sku确认加入到购物车
|
||||
const skuAddToCart = (sku: any, merchantId: string) => {
|
||||
cartStore.addGoodsItemToCart(sku)
|
||||
cartStore.updateShopCart(merchantId)
|
||||
}
|
||||
|
||||
// 获取购物车列表
|
||||
const getShopCart = async (merchantId: string) => {
|
||||
await cartStore.getShopCart(merchantId)
|
||||
}
|
||||
|
||||
// 添加一个商品数量
|
||||
const addProductCount = async (productId: string, merchantId: string) => {
|
||||
if (!merchantId) return
|
||||
await cartStore.addProductCount(productId, merchantId)
|
||||
}
|
||||
|
||||
// 减少一个商品数量
|
||||
const removeProductCount = async (productId: string, merchantId: string) => {
|
||||
if (!merchantId) return
|
||||
await cartStore.removeProductCount(productId, merchantId)
|
||||
}
|
||||
|
||||
const goSettlement = (mchId: string) => {
|
||||
uni.navigateTo({ url: `/pages/prosperity-shop/settlement?mchId=${mchId}` })
|
||||
}
|
||||
|
||||
// 点击提交订单
|
||||
const doBuy = async (pickUpId: string) => {
|
||||
if (isEmpty(cartList.value)) return uni.showToast({ title: "请至少购买一件商品", icon: "none" })
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "是否确定购买",
|
||||
confirmColor: "#0094ff",
|
||||
success: async (res) => {
|
||||
if (!res.confirm) return
|
||||
submitPay(pickUpId)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 确认提交订单
|
||||
const submitPay = async (pickUpId: string) => {
|
||||
try {
|
||||
uni.showLoading({ title: "支付中...", mask: false })
|
||||
const { code, msg }: any = await orderApi.appCreateFarmerReserveOrder({
|
||||
carParentId: cartList.value[0].parentCarId,
|
||||
pickUpId,
|
||||
})
|
||||
if (code != 0) throw new Error(msg)
|
||||
uni.showToast({ title: "支付成功", icon: "none" })
|
||||
router.jump("/pages/tabbar/order")
|
||||
} catch (error: any) {
|
||||
uni.showToast({ title: error.message, icon: "none" })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
totPrice,
|
||||
totalCount,
|
||||
cartList,
|
||||
getShopCart,
|
||||
skuAddToCart,
|
||||
addProductCount,
|
||||
removeProductCount,
|
||||
goSettlement,
|
||||
doBuy,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { prosperityShopApi } from "@/sheep/api"
|
||||
import { ref } from "vue"
|
||||
|
||||
export function useCategaryManage() {
|
||||
const categoryList = ref<any>([])
|
||||
|
||||
// 获取商品分类
|
||||
const getGoodsCategory = async () => {
|
||||
const { code, data, msg } = await prosperityShopApi.getGoodsCategory()
|
||||
if (code != 0) return uni.showToast({ title: msg || "商品分类获取失败", icon: "none" })
|
||||
categoryList.value = data
|
||||
}
|
||||
// 删除分类
|
||||
const delCategary = async (id: string) => {
|
||||
if (!id) return uni.showToast({ title: "分类id不存在", icon: "none" })
|
||||
uni.showModal({
|
||||
title: "确定删除分类吗?",
|
||||
content: "",
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showLoading({ title: "正在删除" })
|
||||
const { code, msg } = await prosperityShopApi.delCategary(id)
|
||||
if (code != 0) return uni.showToast({ title: msg || "删除失败", icon: "none" })
|
||||
uni.showToast({ title: "删除成功", icon: "none" })
|
||||
getGoodsCategory()
|
||||
uni.hideLoading()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return { categoryList, getGoodsCategory, delCategary }
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { prosperityShopApi } from "@/sheep/api"
|
||||
|
||||
export function useGoodsManage(pageRef) {
|
||||
// 新增商品
|
||||
const addGoods = () => {
|
||||
uni.navigateTo({ url: `/pages/prosperity-shop/goods-form` })
|
||||
}
|
||||
// 编辑商品
|
||||
const editGoods = (id: string) => {
|
||||
if (!id) return uni.showToast({ title: "商品id不存在", icon: "none" })
|
||||
uni.navigateTo({ url: `/pages/prosperity-shop/goods-form?id=${id}` })
|
||||
}
|
||||
// 删除商品
|
||||
const deleteGoods = (id: string) => {
|
||||
if (!id) return uni.showToast({ title: "商品id不存在", icon: "none" })
|
||||
uni.showModal({
|
||||
title: "确定删除商品吗?",
|
||||
content: "",
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
const { code } = await prosperityShopApi.deleteGoods(id)
|
||||
if (code != 0) return uni.showToast({ title: "删除失败", icon: "error" })
|
||||
uni.showToast({ title: "删除成功", icon: "none" })
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
// 查看详情
|
||||
const doGoodsDetail = (id: string, merchantId: string) => {
|
||||
if (!id) return uni.showToast({ title: "商品id不存在", icon: "none" })
|
||||
uni.navigateTo({
|
||||
url: `/pages/prosperity-shop/goods-detail?id=${id}&mchId=${merchantId}`,
|
||||
})
|
||||
}
|
||||
// 分类管理
|
||||
const categaryManage = () => {
|
||||
uni.navigateTo({ url: "/pages/prosperity-shop/categary-manage" })
|
||||
}
|
||||
|
||||
return { addGoods, editGoods, deleteGoods, doGoodsDetail, categaryManage }
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { computed, ref } from "vue"
|
||||
|
||||
interface scrollDetail {
|
||||
deltaX: number
|
||||
deltaY: number
|
||||
scrollHeight: number
|
||||
scrollLeft: number
|
||||
scrollTop: number
|
||||
scrollWidth: number
|
||||
}
|
||||
|
||||
export default function useShopDetailScroll() {
|
||||
const scrollDetail = ref<scrollDetail>({} as scrollDetail)
|
||||
|
||||
const onScroll = (e: any) => {
|
||||
scrollDetail.value = e.detail
|
||||
}
|
||||
|
||||
const filterStyle = computed(() => {
|
||||
if (!scrollDetail.value.scrollTop) return {}
|
||||
const scrollY = scrollDetail.value.scrollTop
|
||||
const blur = Math.min(1, scrollY / 400) * 10
|
||||
return {
|
||||
backdropFilter: `blur(${blur}rpx)`,
|
||||
}
|
||||
})
|
||||
|
||||
const searchStyle = computed(() => {
|
||||
if (!scrollDetail.value.scrollTop) return {}
|
||||
const minWidth = 10
|
||||
const scrollY = scrollDetail.value.scrollTop
|
||||
const scrollHeight = scrollDetail.value.scrollHeight
|
||||
const scrollWidth = scrollDetail.value.scrollWidth
|
||||
const calcWidth = Math.min(((scrollY / scrollHeight) * scrollWidth) / 2, scrollWidth)
|
||||
if (isNaN(calcWidth)) return { width: `${minWidth}%` }
|
||||
if (calcWidth < 2) return { width: `${calcWidth + minWidth}%` }
|
||||
return {
|
||||
width: `${calcWidth + minWidth}%`,
|
||||
border: `2rpx solid #1879ff`,
|
||||
opacity: (calcWidth / 100) * 6,
|
||||
}
|
||||
})
|
||||
|
||||
return { scrollDetail, onScroll, filterStyle, searchStyle }
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<crud-layout bg="/static/page/blue-bg.png">
|
||||
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="pageHook.list" :default-page-size="10"
|
||||
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
|
||||
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
|
||||
<template #top>
|
||||
<view class="flex justify-between items-center my-8rpx">
|
||||
<tm-text font-size="40" color="#0779FD" class="font-bold">共富商城</tm-text>
|
||||
<tm-text font-size="30" color="#0779FD">优质商户,品质商品,欢迎联系购买</tm-text>
|
||||
</view>
|
||||
<view
|
||||
class="w-full flex items-center mb-16rpx border-4rpx border-solid border-#0779fd rounded-14rpx p-r-10rpx">
|
||||
<tm-input class="flex-1" v-model="state.params.keyword" type="text" height="72" minHeight="72"
|
||||
fontSize="28" :showClear="true" :focus="false" placeholder="请输入超市名称" :focus-highlight="false"
|
||||
color="#ECF4F9" width="100%">
|
||||
<template v-slot:rightIcon>
|
||||
<tm-button class="mr-8rpx px-24rpx py-26rpx text-30rpx!" size="xs" @click="search">搜
|
||||
索</tm-button>
|
||||
</template>
|
||||
</tm-input>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view class="card p-20rpx mb-20rpx" v-for="(v, i) in pageHook.list" :key="i" @tap="toDetail(v)">
|
||||
<view class="flex">
|
||||
<image class="w-160rpx h-160rpx bg-#eeeeee rounded-12rpx" :src="filePath(v.images)"
|
||||
mode="aspectFill" />
|
||||
<view class="ml-20rpx flex-1 overflow-hidden">
|
||||
<view class="sub-text-32 truncate">{{ v.name }}</view>
|
||||
<view class="second-text-26 flex justify-between my-6rpx">
|
||||
<view>
|
||||
<image class="w-24rpx h-24rpx mr-8rpx" src="@/static/page/phone.svg" />
|
||||
<text @tap.stop="doPhone(v.contactPhone)">{{ v.contactPhone || "暂无" }}</text>
|
||||
</view>
|
||||
<text class="c-#0094ff! sub-text-26 px-16rpx bg-#0094ff22 rounded-2">营业</text>
|
||||
</view>
|
||||
<view class="second-text-26">
|
||||
<image class="w-24rpx h-24rpx mr-8rpx" src="@/static/page/address.svg" />
|
||||
<text>{{ v.address || "暂无" }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view scroll-x>
|
||||
<view class="flex mt-16rpx">
|
||||
<view class="w-180rpx flex-col items-center mr-20rpx" v-for="(pV, pI) in v.featuredProducts"
|
||||
:key="pI">
|
||||
<image class="size-180rpx rounded-12rpx bg-#eeeeee" :src="filePath(pV.coverImage)"
|
||||
mode="aspectFill" />
|
||||
<view class="w-full mt-4rpx sub-text-24 truncate">{{ pV.title }}</view>
|
||||
<view class="w-full flex justify-between">
|
||||
<text class="text-26 color-red!">
|
||||
<text class="text-22rpx">¥</text>
|
||||
<text>{{ pV.priceType == "1" ? displayPrice(pV.minPrice, "") :
|
||||
displayPrice(pV.spuPrice, "")
|
||||
}}</text>
|
||||
</text>
|
||||
</view>
|
||||
<view class="w-full flex justify-between second-text-24">
|
||||
<text>库存{{ pV.totalStock }}</text>
|
||||
<text>{{ pV.status ? "已停售" : "在售" }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</z-paging>
|
||||
</crud-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { hooks, router } from "@/sheep"
|
||||
import { prosperityShopApi } from "@/sheep/api"
|
||||
import { onLoad } from "@dcloudio/uni-app"
|
||||
import { isEmpty } from "radash"
|
||||
import { reactive, ref } from "vue"
|
||||
import { filePath } from "@@/utils/file"
|
||||
import { displayPrice } from "@/sheep/utils/format-money"
|
||||
|
||||
const state = reactive<any>({
|
||||
params: {
|
||||
keyword: "",
|
||||
},
|
||||
shopList: [],
|
||||
})
|
||||
const pageRef = ref<any>(null)
|
||||
const pageHook = hooks.usePage(pageRef, prosperityShopApi.getShopList, null, "data")
|
||||
|
||||
const query = (page: number, pagesize: number) => {
|
||||
pageHook.startPage(page, pagesize, false, state.params)
|
||||
}
|
||||
|
||||
onLoad(async () => {
|
||||
})
|
||||
|
||||
// 切换分类
|
||||
// const doClassify = async (categoryId: string) => {
|
||||
// if (state.params.categoryId == categoryId) return
|
||||
// state.params.categoryId = categoryId
|
||||
// pageRef.value && pageRef.value.reload(false)
|
||||
// }
|
||||
|
||||
// 点击搜索
|
||||
const search = () => {
|
||||
// state.params.categoryId = ""
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
}
|
||||
|
||||
// 联系商家
|
||||
const doPhone = async (item: string) => {
|
||||
if (isEmpty(item)) return uni.showToast({ title: "该商户没有预留电话", icon: "none" })
|
||||
hooks.useMakePhoneCall(item.trim())
|
||||
}
|
||||
|
||||
// 超市详情
|
||||
const toDetail = async (v: any) => {
|
||||
const isLogin = await hooks.useCheckLogin()
|
||||
if (isLogin) {
|
||||
router.jump("/pages/prosperity-shop/shop-detail", { id: v.id })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,129 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from "vue"
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app"
|
||||
import { merchantApi } from "@@/api"
|
||||
import { hooks, router } from "@@/index"
|
||||
import { filePath } from "@@/utils/file"
|
||||
import zTabs from "@@/components/other/z-tabs/z-tabs.vue"
|
||||
|
||||
const pageRef = ref<any>(null)
|
||||
const state = reactive<any>({
|
||||
pa: {
|
||||
pagesize: 10,
|
||||
// 0-其他食堂
|
||||
// 1-自营食堂
|
||||
// 2-合作商家
|
||||
// 3-自营商家
|
||||
// 4-第三方应用
|
||||
// 5-共富商城(助农)
|
||||
type: "5",
|
||||
name: "",
|
||||
catgoryId: null,
|
||||
},
|
||||
tabs: [{ id: null, name: "全部", disabled: false }],
|
||||
})
|
||||
const pageHook = hooks.usePage(pageRef, merchantApi.page, null, "data.records")
|
||||
|
||||
const query = (page: number, pagesize: number) => {
|
||||
pageHook.startPage(page, pagesize, false, state.pa)
|
||||
}
|
||||
|
||||
onLoad(async () => {
|
||||
try {
|
||||
const res: any = await merchantApi.selectCatgory({ current: 1, size: 99999 })
|
||||
if (res.code == 0) {
|
||||
state.tabs = state.tabs.concat(res.data)
|
||||
} else {
|
||||
state.tabs = [{ id: null, name: "全部", disabled: false }]
|
||||
}
|
||||
} catch (error: any) {
|
||||
uni.showToast({ title: error, icon: "none" })
|
||||
}
|
||||
})
|
||||
onShow(() => {
|
||||
pageRef.value && pageRef.value.reload(false)
|
||||
})
|
||||
|
||||
const search = () => {
|
||||
pageRef.value && pageRef.value.reload(true)
|
||||
}
|
||||
|
||||
const doTab = (e: any) => {
|
||||
const target = state.tabs[e]
|
||||
state.pa.catgoryId = target.id
|
||||
pageRef.value.reload(true)
|
||||
}
|
||||
|
||||
const toDetail = async (v: any) => {
|
||||
const isLogin = await hooks.useCheckLogin()
|
||||
if (isLogin) {
|
||||
if (!v.supType) {
|
||||
router.jump("/pages/merchant/detail", { id: v.id })
|
||||
return
|
||||
}
|
||||
if (v.supType && v.supType == "10") return router.jump("/pages/merchant/brandDetail", { parentId: v.id })
|
||||
if (v.supType && v.supType == "20") return router.jump("/pages/merchant/detail", { id: v.id })
|
||||
uni.showToast({ title: "合作商家类型未知", icon: "none" })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<crud-layout bg="/static/page/blue-bg.png">
|
||||
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="pageHook.list" :default-page-size="10"
|
||||
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
|
||||
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false" @scroll="pageHook.scroll">
|
||||
<template #top>
|
||||
<view class="h-30rpx" />
|
||||
<view class="flex w-full">
|
||||
<tm-input class="flex-1" v-model="state.pa.name" 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">
|
||||
<template v-slot:rightIcon>
|
||||
<tm-button class="mr-8rpx px-24rpx py-26rpx text-30rpx!" size="xs" @click="search">搜
|
||||
索</tm-button>
|
||||
</template>
|
||||
</tm-input>
|
||||
</view>
|
||||
<view class="bg-white rounded-16rpx px-20rpx my-20rpx">
|
||||
<z-tabs :list="state.tabs" @change="doTab" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view class="flex-row card p-20rpx m-b-20rpx" v-for="(v, i) in pageHook.list" :key="i" @tap="toDetail(v)">
|
||||
<image class="w-180rpx h-180rpx rounded-12rpx my-auto" :src="filePath(v.logoPic)" mode="aspectFill" />
|
||||
<view class="flex-1 flex-col m-l-20rpx">
|
||||
<view class="sub-text-34">
|
||||
<text class="mr-16rpx">{{ v.name }}</text>
|
||||
<!-- <tm-tag v-if="v.supType == '10' && Number(v.mchCount) > 0" size="xs" skin="thin">共{{ v.mchCount }}家</tm-tag> -->
|
||||
</view>
|
||||
<view class="m-t-10rpx">
|
||||
<view class="flex justify-between items-center">
|
||||
<view class="flex-1">
|
||||
<view v-if="v.phone" class="flex items-center">
|
||||
<image class="w-24rpx h-24rpx" src="@/static/page/phone.svg" />
|
||||
<view class="second-text-28 ml-8rpx">{{ v.phone }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<tm-tag class="max-w-160rpx" size="xs">{{ v.deptName.length > 5 ? v.deptName.substring(0, 5)
|
||||
+ "..." :
|
||||
v.deptName }}</tm-tag>
|
||||
</view>
|
||||
<view class="second-text-24">
|
||||
<view v-if="v.jobTime != ',,'" class="second-text-28">营业时间:</view>
|
||||
<view>{{ v.jobTime?.split(",")[0] ? "早:" + v.jobTime?.split(",")[0] : "" }}</view>
|
||||
<view>{{ v.jobTime?.split(",")[1] ? "中:" + v.jobTime?.split(",")[1] : "" }}</view>
|
||||
<view>{{ v.jobTime?.split(",")[2] ? "晚:" + v.jobTime?.split(",")[2] : "" }}</view>
|
||||
</view>
|
||||
<view v-if="v.addressDes" class="second-text-28">
|
||||
<image class="w-24rpx h-24rpx" src="@/static/page/address.svg" />
|
||||
<text class="ml-8rpx">地址:</text>
|
||||
<text class="m-l-10rpx">{{ v.addressDes }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</z-paging>
|
||||
</crud-layout>
|
||||
</template>
|
||||
@@ -0,0 +1,104 @@
|
||||
import request from "@/sheep/utils/request"
|
||||
|
||||
export default {
|
||||
// 获取超市列表
|
||||
getShopList: (params: { productLimit: number }): Promise<any> => {
|
||||
return request({
|
||||
url: "/supermarket/app/mall/merchant/list",
|
||||
method: "GET",
|
||||
params: { productLimit: 5 },
|
||||
})
|
||||
},
|
||||
// 获取商品分类列表
|
||||
getGoodsCategory: (params?: any): Promise<any> => {
|
||||
return request({
|
||||
url: "/supermarket/app/mall/merchant/category/list",
|
||||
method: "GET",
|
||||
params,
|
||||
})
|
||||
},
|
||||
// 获取超市详情
|
||||
getShopDetail: (id: string): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/merchant/${id}`,
|
||||
method: "GET",
|
||||
})
|
||||
},
|
||||
// 获取超市商品分类列表树
|
||||
getGoodsCategoryTree: (params?: { merchantId: string }): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/category/tree`,
|
||||
method: "GET",
|
||||
params,
|
||||
})
|
||||
},
|
||||
// 获取商品列表
|
||||
getGoodsList: (params: any): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/product/page`,
|
||||
method: "GET",
|
||||
params,
|
||||
})
|
||||
},
|
||||
// 获取商品详情
|
||||
getGoodsDetail: (id: string): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/product/${id}`,
|
||||
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({
|
||||
url: `/supermarket/app/mall/merchant/product/${id}`,
|
||||
method: "DELETE",
|
||||
})
|
||||
},
|
||||
// 新增商品
|
||||
addGoods: (data: any): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/merchant/product/create`,
|
||||
method: "POST",
|
||||
data,
|
||||
})
|
||||
},
|
||||
// 修改商品
|
||||
editGoods: (data: any): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/merchant/product/update`,
|
||||
method: "PUT",
|
||||
data,
|
||||
})
|
||||
},
|
||||
// 删除商品分类
|
||||
delCategary: (id: any): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/merchant/category/${id}`,
|
||||
method: "DELETE",
|
||||
})
|
||||
},
|
||||
// 新增商品分类
|
||||
addCategary: (data: any): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/merchant/category/create`,
|
||||
method: "POST",
|
||||
data,
|
||||
})
|
||||
},
|
||||
// 编辑商品分类
|
||||
editCategary: (data: any): Promise<any> => {
|
||||
return request({
|
||||
url: `/supermarket/app/mall/merchant/category/update`,
|
||||
method: "PUT",
|
||||
data,
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<view class="tree-container">
|
||||
<view v-for="item in nodeData" :key="item.id">
|
||||
<view class="tree-item" :style="item.id == selectedNodeId ? { borderColor: '#1879ff' } : { color: '#000' }"
|
||||
@click.stop="toCurrentNode(item)">
|
||||
<image class="tree-item-icon" :src="filePath(item.picUrl)" />
|
||||
<text>{{ item.name }}</text>
|
||||
</view>
|
||||
|
||||
<node-tree v-model="selectedNodeId" v-if="item.children && item.children.length" :node-data="item.children"
|
||||
:style="{ marginLeft: `${getLevel() * 60}rpx` }" @currentNode="(v) => emit('currentNode', v)" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getCurrentInstance } from "vue"
|
||||
import { filePath } from "../utils/file"
|
||||
import NodeTree from "./node-tree.vue"
|
||||
|
||||
withDefaults(defineProps<{ nodeData: any[] }>(), { nodeData: () => [] })
|
||||
const emit = defineEmits(["currentNode"])
|
||||
const selectedNodeId = defineModel<any>("modelValue")
|
||||
defineOptions({ name: "node-tree-item" })
|
||||
|
||||
const compInstance = getCurrentInstance()
|
||||
|
||||
const toCurrentNode = (item) => {
|
||||
emit("currentNode", { ...item, level: getLevel() })
|
||||
selectedNodeId.value = item.id
|
||||
}
|
||||
|
||||
const getLevel = () => {
|
||||
let level = 1
|
||||
let $parent = compInstance?.parent
|
||||
while ($parent && $parent.proxy?.$options.name === "node-tree-item") {
|
||||
level++
|
||||
$parent = $parent.parent
|
||||
}
|
||||
return level
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tree-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tree-item {
|
||||
border: 4rpx solid #e4e7ed;
|
||||
margin-bottom: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10rpx 20rpx;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.tree-item-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<view class="mb-20rpx">
|
||||
<node-tree v-model="selectedNodeId" :node-data="categoryTree" @currentNode="currentNode" />
|
||||
<view v-if="categoryTree.length === 0" class="text-center text-#888 py-40rpx">暂无数据</view>
|
||||
</view>
|
||||
<view class="px-20rpx py-16rpx flex justify-between sticky bottom-0 bg-white">
|
||||
<tm-button skin="thin" block @click="emit('cancel')">取消</tm-button>
|
||||
|
||||
<tm-button block :disabled="!selectedNode" @click="onConfirm">确定</tm-button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import NodeTree from "@/sheep/components/node-tree.vue"
|
||||
|
||||
defineProps({
|
||||
categoryTree: {
|
||||
type: Array<any>,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
const selectedNodeId = defineModel("modelValue")
|
||||
const emit = defineEmits(["confirm", "cancel"])
|
||||
|
||||
const selectedNode = ref<any>({})
|
||||
|
||||
const currentNode = (currentNode) => {
|
||||
selectedNode.value = currentNode
|
||||
}
|
||||
|
||||
function onConfirm() {
|
||||
if (!selectedNode.value) {
|
||||
return uni.showToast({ title: "请选择分类", icon: "none" })
|
||||
}
|
||||
emit("confirm", { categoryId: selectedNode.value.id, categoryName: selectedNode.value.name })
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,11 @@
|
||||
const treeToList = <T extends { children?: T[] }>(tree: T[], result: T[] = []): T[] => {
|
||||
for (const node of tree) {
|
||||
result.push(node)
|
||||
if (node?.children?.length) {
|
||||
treeToList(node.children, result)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export default treeToList
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 8.8 KiB |