feat(商品属性): 多 sku

This commit is contained in:
unknown 2024-06-11 00:34:43 +08:00
parent b10f8458af
commit b999d59bfc
11 changed files with 802 additions and 767 deletions

View File

@ -1,43 +1,21 @@
<template> <template>
<pb-layout <pb-layout class="product-list" title="产品" navbar="normal" tabbar="/pages/index/product" :bgStyle="bgStyle"
class="product-list" opacityBgUi="bg-white" color="black">
title="产品" <view v-if="state.pagination.total > 0" class="goods-list ss-m-t-20">
navbar="normal" <view class="ss-p-l-20 ss-p-r-20 ss-m-b-20" v-for="item in state.pagination.list" :key="item.id">
tabbar="/pages/index/product" <p-goods-column size="lg" :data="item" :topRadius="10" :bottomRadius="10"
:bgStyle="bgStyle" @click="peach.$router.go('/pages/product/manageGoods', { id: item.id, mark: 'detail' })" />
opacityBgUi="bg-white" </view>
color="black" </view>
>
<view v-if="state.pagination.total > 0" class="goods-list ss-m-t-20">
<view class="ss-p-l-20 ss-p-r-20 ss-m-b-20" v-for="item in state.pagination.list" :key="item.id">
<p-goods-column
size="lg"
:data="item"
:topRadius="10"
:bottomRadius="10"
@click="peach.$router.go('/pages/product/manageGoods', { id: item.id, mark: 'detail' })"
/>
</view>
</view>
<uni-load-more <uni-load-more v-if="state.pagination.total > 0" :status="state.loadStatus" :content-text="{
v-if="state.pagination.total > 0" contentdown: '上拉加载更多',
:status="state.loadStatus" }" @click="loadMore" />
:content-text="{
contentdown: '上拉加载更多',
}"
@click="loadMore"
/>
<view class="_icon-add-round add-product" @click="addGoods"></view> <view class="_icon-add-round add-product" @click="addGoods"></view>
<p-empty <p-empty v-if="state.pagination.total === 0" icon="/static/soldout-empty.png" text="暂无产品" bgColor="transparent" />
v-if="state.pagination.total === 0" </pb-layout>
icon="/static/soldout-empty.png"
text="暂无产品"
bgColor="transparent"
/>
</pb-layout>
</template> </template>
<script setup> <script setup>
@ -49,165 +27,167 @@ import _ from 'lodash'
import { resetPagination } from '@/peach/utils' import { resetPagination } from '@/peach/utils'
const bgStyle = { const bgStyle = {
backgroundImage: '', backgroundImage: '',
backgroundColor: 'var(--ui-BG-1)', backgroundColor: 'var(--ui-BG-1)',
description: '', description: '',
} }
const state = ref({ const state = ref({
pagination: { pagination: {
list: [], list: [],
total: 0, total: 0,
pageNo: 1, pageNo: 1,
pageSize: 6, pageSize: 6,
name: '', name: '',
createTime: [], createTime: [],
}, },
loadStatus: '', loadStatus: '',
}) })
function emptyList() { function emptyList() {
resetPagination(state.value.pagination) resetPagination(state.value.pagination)
} }
function onSearch() { function onSearch() {
emptyList() emptyList()
getList() getList()
} }
async function getList() { async function getList() {
let { data } = await GoodApi.getProductList({ let { data } = await GoodApi.getProductList({
pageNo: state.value.pagination.pageNo, pageNo: state.value.pagination.pageNo,
pageSize: state.value.pagination.pageSize, pageSize: state.value.pagination.pageSize,
}) })
state.value.pagination.list = _.concat(state.value.pagination.list, data.list) state.value.pagination.list = _.concat(state.value.pagination.list, data.list)
state.value.pagination.total = data.total state.value.pagination.total = data.total
let currentPageTotal = state.value.pagination.length let currentPageTotal = state.value.pagination.length
state.value.loadStatus = currentPageTotal < state.value.pagination.total ? 'more' : 'noMore' state.value.loadStatus = currentPageTotal < state.value.pagination.total ? 'more' : 'noMore'
} }
function addGoods() { function addGoods() {
peach.$router.go('/pages/product/manageGoods') peach.$router.go('/pages/product/manageGoods', {
title: '添加商品'
})
} }
function loadMore() { function loadMore() {
if (state.value.loadStatus === 'noMore') { if (state.value.loadStatus === 'noMore') {
return return
} }
state.value.pagination.pageNo++ state.value.pagination.pageNo++
getList() getList()
} }
onLoad(async (options) => { onLoad(async (options) => {
getList() getList()
}) })
onReachBottom(() => { onReachBottom(() => {
loadMore() loadMore()
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.product-list { .product-list {
.add-product { .add-product {
position: fixed; position: fixed;
color: var(--ui-BG-Main); color: var(--ui-BG-Main);
bottom: 70px; bottom: 70px;
right: 20px; right: 20px;
font-size: 80rpx; font-size: 80rpx;
}
.goods-list-box {
width: 50%;
box-sizing: border-box;
.left-list {
margin-right: 10rpx;
margin-bottom: 20rpx;
} }
.goods-list-box { .right-list {
width: 50%; margin-left: 10rpx;
box-sizing: border-box; margin-bottom: 20rpx;
}
}
.left-list { .goods-box {
margin-right: 10rpx; &:nth-last-of-type(1) {
margin-bottom: 20rpx; margin-bottom: 0 !important;
}
.right-list {
margin-left: 10rpx;
margin-bottom: 20rpx;
}
} }
.goods-box { &:nth-child(2n) {
&:nth-last-of-type(1) { margin-right: 0;
margin-bottom: 0 !important; }
} }
&:nth-child(2n) { .list-icon {
margin-right: 0; width: 80rpx;
}
.sicon-goods-card {
font-size: 40rpx;
} }
.list-icon { .sicon-goods-list {
width: 80rpx; font-size: 40rpx;
}
}
.sicon-goods-card { .goods-card {
font-size: 40rpx; margin-left: 20rpx;
} }
.sicon-goods-list { .list-filter-tabs {
font-size: 40rpx; background-color: #fff;
} }
.filter-list-box {
padding: 28rpx 52rpx;
.filter-item {
font-size: 28rpx;
font-weight: 500;
color: #333333;
line-height: normal;
margin-bottom: 24rpx;
&:nth-last-child(1) {
margin-bottom: 0;
}
} }
.goods-card { .filter-item-active {
margin-left: 20rpx; color: var(--ui-BG-Main);
}
}
.tab-item {
height: 50px;
position: relative;
z-index: 11;
.tab-title {
font-size: 30rpx;
} }
.list-filter-tabs { .cur-tab-title {
background-color: #fff; font-weight: $font-weight-bold;
} }
.filter-list-box { .tab-line {
padding: 28rpx 52rpx; width: 60rpx;
height: 6rpx;
.filter-item { border-radius: 6rpx;
font-size: 28rpx; position: absolute;
font-weight: 500; left: 50%;
color: #333333; transform: translateX(-50%);
line-height: normal; bottom: 10rpx;
margin-bottom: 24rpx; background-color: var(--ui-BG-Main);
z-index: 12;
&:nth-last-child(1) {
margin-bottom: 0;
}
}
.filter-item-active {
color: var(--ui-BG-Main);
}
}
.tab-item {
height: 50px;
position: relative;
z-index: 11;
.tab-title {
font-size: 30rpx;
}
.cur-tab-title {
font-weight: $font-weight-bold;
}
.tab-line {
width: 60rpx;
height: 6rpx;
border-radius: 6rpx;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 10rpx;
background-color: var(--ui-BG-Main);
z-index: 12;
}
} }
}
} }
</style> </style>

View File

@ -11,29 +11,31 @@
</template> </template>
<uni-forms-item label="商品封面图" name="picUrl" label-position="top"> <uni-forms-item label="商品封面图" name="picUrl" label-position="top">
<p-uploader v-model:url="formData.picUrl" fileMediatype="image" limit="1" mode="grid" <p-uploader v-model:url="formData.picUrl" :readonly="!canEdit" fileMediatype="image" limit="1" mode="grid"
:imageStyles="{ width: '168rpx', height: '168rpx' }" /> :imageStyles="{ width: '168rpx', height: '168rpx' }" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="商品条码" name="barCode"> <uni-forms-item label="商品条码" name="barCode">
<uni-easyinput type="text" trim="all" v-model="formData.barCode" placeholder="请输入商品条码" /> <uni-easyinput type="text" trim="all" v-model="formData.barCode" :disabled="!canEdit" placeholder="请输入商品条码" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="销售价" name="price"> <uni-forms-item label="销售价" name="price">
<uni-easyinput type="number" trim="all" v-model="formData.price" placeholder="请输入商品销售价" /> <uni-easyinput type="number" trim="all" v-model="formData.price" :disabled="!canEdit" placeholder="请输入商品销售价" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="市场价" name="marketPrice"> <uni-forms-item label="市场价" name="marketPrice">
<uni-easyinput type="number" trim="all" v-model="formData.marketPrice" placeholder="请输入商品销售价" /> <uni-easyinput type="number" trim="all" v-model="formData.marketPrice" :disabled="!canEdit"
placeholder="请输入商品销售价" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="成本价" name="costPrice"> <uni-forms-item label="成本价" name="costPrice">
<uni-easyinput type="number" trim="all" v-model="formData.costPrice" placeholder="请输入商品销售价" /> <uni-easyinput type="number" trim="all" v-model="formData.costPrice" :disabled="!canEdit"
placeholder="请输入商品销售价" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="库存" name="stock"> <uni-forms-item label="库存" name="stock">
<uni-easyinput type="number" trim="all" v-model="formData.stock" placeholder="请输入商品库存" /> <uni-easyinput type="number" trim="all" v-model="formData.stock" :disabled="!canEdit" placeholder="请输入商品库存" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="重量kg" name="weight"> <uni-forms-item label="重量kg" name="weight">
<uni-easyinput type="number" trim="all" v-model="formData.weight" placeholder="请输入商品重量" /> <uni-easyinput type="number" trim="all" v-model="formData.weight" :disabled="!canEdit" placeholder="请输入商品重量" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="体积" name="volume"> <uni-forms-item label="体积" name="volume">
<uni-easyinput type="number" trim="all" v-model="formData.volume" placeholder="请输入商品体积" /> <uni-easyinput type="number" trim="all" v-model="formData.volume" :disabled="!canEdit" placeholder="请输入商品体积" />
</uni-forms-item> </uni-forms-item>
</uni-forms> </uni-forms>
</div> </div>
@ -42,6 +44,7 @@
<script setup> <script setup>
import { ref, watch, computed, defineProps } from 'vue' import { ref, watch, computed, defineProps } from 'vue'
import peach from '@/peach' import peach from '@/peach'
import { canEdit } from '../js/sku';
const props = defineProps({ const props = defineProps({
skus: { skus: {
type: Array, type: Array,
@ -53,6 +56,11 @@ const specType = computed(() => peach.$store("trade").goodsInfo.specType);
watch(() => props.skus, (newVal) => { watch(() => props.skus, (newVal) => {
console.log(newVal) console.log(newVal)
// sku
if (!specType.value) {
formData.value = newVal[0] ?? {}
return
}
formData.value = newVal ?? {} formData.value = newVal ?? {}
}, { immediate: true }) }, { immediate: true })

View File

@ -1,84 +1,82 @@
<template> <template>
<view class="property-detail"> <view class="property-detail">
<template v-for="item in goodsPropertyList" :key="item.id"> <template v-for="item in goodsPropertyList" :key="item.id">
<view class="property-item ss-p-40 ss-gap-40" v-if="item.checked"> <view class="property-item ss-p-40 ss-gap-40" v-if="item.checked">
<view class="property-name ss-flex ss-gap-40 ss-m-b-20"> <view class="property-name ss-flex ss-gap-40 ss-m-b-20">
<view class="property-name-text">规格名</view> <view class="property-name-text">规格名</view>
<view class="property-name-value">{{ item.name }}</view> <view class="property-name-value">{{ item.name }}</view>
</view> </view>
<view class="property-value ss-flex ss-gap-40"> <view class="property-value ss-flex ss-gap-40">
<view class="property-value-text">规格值</view> <view class="property-value-text">规格值</view>
<view class="property-value-value ss-flex ss-gap-10"> <view class="property-value-value ss-flex ss-gap-10">
<view <view v-for="sitem in item.propertyValues" @tap="chooseProperty(sitem)"
v-for="sitem in item.propertyValues" :class="['item', sitem.checked ? 'active' : '']">{{ sitem.name }}</view>
@tap="chooseProperty(sitem)" </view>
:class="['item', sitem.checked ? 'active' : '']" </view>
>{{ sitem.name }}</view </view>
> </template>
</view> </view>
</view>
</view>
</template>
</view>
</template> </template>
<script setup> <script setup>
import { defineProps, defineEmits, ref, computed } from 'vue' import { defineProps, defineEmits, ref, computed } from 'vue'
import { goodsPropertyList } from '../js/sku' import { goodsPropertyList, canEdit } from '../js/sku'
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
goodsPropertyList: { goodsPropertyList: {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
}) })
const emit = defineEmits(['changeSubProperty']) const emit = defineEmits(['changeSubProperty'])
function chooseProperty(item) { function chooseProperty(item) {
if (canEdit.value) {
item.checked = !item.checked item.checked = !item.checked
emit('changeSubProperty') emit('changeSubProperty')
}
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.property-detail { .property-detail {
.property-item { .property-item {
margin: 0 40rpx 20rpx; margin: 0 40rpx 20rpx;
background-color: #f9f9f9; background-color: #f9f9f9;
border-radius: 10px; border-radius: 10px;
.property-name { .property-name {
.property-name-text { .property-name-text {
color: #606266; color: #606266;
} }
}
.property-value {
.property-value-text {
color: #606266;
}
.property-value-value {
.item {
text-align: center;
line-height: 60rpx;
height: 60rpx;
padding: 0 10px;
border-radius: 10px;
background-color: var(--ui-BG-4);
}
.active {
color: #fff;
background-color: var(--ui-BG-Main);
}
}
}
} }
.property-value {
.property-value-text {
color: #606266;
}
.property-value-value {
.item {
text-align: center;
line-height: 60rpx;
height: 60rpx;
padding: 0 10px;
border-radius: 10px;
background-color: var(--ui-BG-4);
}
.active {
color: #fff;
background-color: var(--ui-BG-Main);
}
}
}
}
} }
</style> </style>

View File

@ -1,163 +1,179 @@
import { ref, computed } from 'vue' import { ref, computed } from "vue";
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from "@dcloudio/uni-app";
import peach from '@/peach' import peach from "@/peach";
import GoodsApi from '@/peach/api/trade/goods' import GoodsApi from "@/peach/api/trade/goods";
import { SPEC_TYPE } from './config' import { SPEC_TYPE } from "./config";
const pickerRef = ref(null) const pickerRef = ref(null);
// 多属性商品 sku 列表 // 多属性商品 sku 列表
const skus = ref([]) const skus = ref([]);
const propertyList = ref([]) const propertyList = ref([]);
const goodsPropertyList = ref([]) const goodsPropertyList = ref([]);
const propertyListRef = ref(null) const propertyListRef = ref(null);
const canEdit = computed(() => peach.$store("trade").canEdit);
const formData = ref({ const formData = ref({
specType: true, specType: true,
specText: SPEC_TYPE[0].label, specText: SPEC_TYPE[0].label,
}) });
async function showPropertyList() { async function showPropertyList() {
await getGoodsProperty() await getGoodsProperty();
propertyListRef.value.onOpen() propertyListRef.value.onOpen();
} }
function onRDPickerConfirm(e) { function onRDPickerConfirm(e) {
peach.$store('trade').specType = SPEC_TYPE[e.value[0]].value peach.$store("trade").specType = SPEC_TYPE[e.value[0]].value;
formData.value.specText = SPEC_TYPE[e.value[0]].label formData.value.specText = SPEC_TYPE[e.value[0]].label;
formData.value.specType = SPEC_TYPE[e.value[0]].value formData.value.specType = SPEC_TYPE[e.value[0]].value;
} }
function pickerProperty() { function pickerProperty() {
let index = specType.value ? 1 : 0 if (canEdit.value) {
pickerRef.value.onOpen([index]) let index = specType.value ? 1 : 0;
pickerRef.value.onOpen([index]);
}
} }
async function onPropertyConfirm(e) { async function onPropertyConfirm(e) {
await getGoodsProperty() await getGoodsProperty();
console.log(e) console.log(e);
}
function onConfirm() {
console.log(skus.value);
} }
async function getGoodsProperty() { async function getGoodsProperty() {
let { data } = await GoodsApi.getHistoryProperty() let { data } = await GoodsApi.getHistoryProperty();
// 把 propertyList 中 id 相同的属性合并,并去重 // 把 propertyList 中 id 相同的属性合并,并去重
propertyList.value = peach.$store('trade').selectedProperty propertyList.value = peach.$store("trade").selectedProperty;
console.log(propertyList.value) console.log(propertyList.value);
// 根据已经选择数据,设置默认选中 // 根据已经选择数据,设置默认选中
data.forEach((item) => { data.forEach((item) => {
// 判断属性是否已经选中 // 判断属性是否已经选中
let propertyParent = propertyList.value.find((sitem) => sitem?.id === item.id) let propertyParent = propertyList.value.find(
(sitem) => sitem?.id === item.id
);
item.checked = propertyParent ? true : false item.checked = propertyParent ? true : false;
// 如果属性已经选中,查询子类中是否有选中 // 如果属性已经选中,查询子类中是否有选中
if (item.checked) { if (item.checked) {
item.propertyValues.forEach((child) => { item.propertyValues.forEach((child) => {
let childResult = propertyParent?.children.some((schild) => schild === child.id) let childResult = propertyParent?.children.some(
child.checked = childResult ? true : false (schild) => schild === child.id
}) );
} child.checked = childResult ? true : false;
}) });
}
});
goodsPropertyList.value = data goodsPropertyList.value = data;
console.log(goodsPropertyList.value) console.log(goodsPropertyList.value);
} }
function changeSubProperty() { function changeSubProperty() {
// 修改子属性状态,需要同步更新 skus 的显示 // 修改子属性状态,需要同步更新 skus 的显示
console.log(goodsPropertyList.value) console.log(goodsPropertyList.value);
// 过滤父属性 checked 选项,深拷贝避免后面循环改变元数据内容 // 过滤父属性 checked 选项,深拷贝避免后面循环改变元数据内容
let temp = JSON.parse(JSON.stringify(goodsPropertyList.value.filter((item) => item.checked))) let temp = JSON.parse(
temp.forEach((item) => { JSON.stringify(goodsPropertyList.value.filter((item) => item.checked))
item.propertyValues = item.propertyValues.filter((child) => child.checked) );
}) temp.forEach((item) => {
item.propertyValues = item.propertyValues.filter((child) => child.checked);
});
let result = temp.map((item) => { let result = temp.map((item) => {
return item.propertyValues.map((child) => ({ return item.propertyValues.map((child) => ({
propertyId: item.id, propertyId: item.id,
propertyName: item.name, propertyName: item.name,
valueId: child.id, valueId: child.id,
valueName: child.name, valueName: child.name,
})) }));
}) });
let tempSkus = [] let tempSkus = [];
for (let item of reduceArr(result)) { for (let item of reduceArr(result)) {
let obj = { let obj = {
picUrl: '', picUrl: "",
barCode: '', barCode: "",
price: 0, price: 0,
marketPrice: 0, marketPrice: 0,
costPrice: 0, costPrice: 0,
stock: 0, stock: 0,
weight: 0, weight: 0,
volume: 0, volume: 0,
properties: item, properties: item,
} };
tempSkus.push(obj) tempSkus.push(obj);
} }
skus.value = tempSkus skus.value = tempSkus;
} }
function reduceArr(arr) { function reduceArr(arr) {
return arr.reduce((acc, cur) => { return arr.reduce((acc, cur) => {
let tempAcc = [] let tempAcc = [];
if (acc.length < 1) { if (acc.length < 1) {
cur.forEach((item, index) => { cur.forEach((item, index) => {
if (tempAcc[index]) { if (tempAcc[index]) {
tempAcc[index].push(item) tempAcc[index].push(item);
} else {
tempAcc[index] = [item]
}
})
} else { } else {
acc.forEach((item, index) => { tempAcc[index] = [item];
cur.forEach((sitem, sindex) => {
tempAcc.push([...item, sitem])
})
})
if (cur.length < 1) {
tempAcc = acc
}
} }
return tempAcc });
}, []) } else {
acc.forEach((item, index) => {
cur.forEach((sitem, sindex) => {
tempAcc.push([...item, sitem]);
});
});
if (cur.length < 1) {
tempAcc = acc;
}
}
return tempAcc;
}, []);
} }
const specType = computed(() => peach.$store('trade').goodsInfo.specType) const specType = computed(() => peach.$store("trade").goodsInfo.specType);
function initial() { function initial() {
onLoad(() => { onLoad(() => {
formData.value.specType = specType.value ? true : false formData.value.specType = specType.value ? true : false;
formData.value.specText = SPEC_TYPE[specType.value ? 1 : 0].label formData.value.specText = SPEC_TYPE[specType.value ? 1 : 0].label;
skus.value = peach.$store('trade').skus skus.value = peach.$store("trade").skus;
if (specType.value) { if (specType.value) {
getGoodsProperty() getGoodsProperty();
} }
}) });
} }
export { export {
initial, initial,
skus, canEdit,
pickerRef, skus,
pickerProperty, pickerRef,
propertyListRef, pickerProperty,
formData, onConfirm,
onRDPickerConfirm, propertyListRef,
onPropertyConfirm, formData,
propertyList, onRDPickerConfirm,
showPropertyList, onPropertyConfirm,
goodsPropertyList, propertyList,
changeSubProperty, showPropertyList,
} goodsPropertyList,
changeSubProperty,
};

View File

@ -1,22 +1,22 @@
<template> <template>
<pb-layout class="manage-goods" title="发布商品" leftIcon="leftIcon" navbar="normal" :bgStyle="bgStyle" <pb-layout class="manage-goods" :title="goodsTitle" leftIcon="leftIcon" navbar="normal" :bgStyle="bgStyle"
opacityBgUi="bg-white" color="black"> opacityBgUi="bg-white" color="black">
<view class="goods-form"> <view class="goods-form">
<uni-forms ref="formRef" v-model="formData" :rules="rules" label-position="top" label-width="160"> <uni-forms ref="formRef" v-model="formData" :rules="rules" label-position="top" label-width="160">
<uni-forms-item label="商品封面图" name="picUrl" required> <uni-forms-item label="商品封面图" name="picUrl" required>
<p-uploader v-model:url="formData.picUrl" fileMediatype="image" limit="1" mode="grid" <p-uploader v-model:url="formData.picUrl" :readonly="!canEdit" fileMediatype="image" limit="1" mode="grid"
:imageStyles="{ width: '168rpx', height: '168rpx' }" /> :imageStyles="{ width: '168rpx', height: '168rpx' }" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="商品轮播图" name="sliderPicUrls" required> <uni-forms-item label="商品轮播图" name="sliderPicUrls" required>
<p-uploader v-model:url="formData.sliderPicUrls" fileMediatype="image" limit="6" mode="grid" <p-uploader v-model:url="formData.sliderPicUrls" :readonly="!canEdit" fileMediatype="image" limit="6"
:imageStyles="{ width: '168rpx', height: '168rpx' }" /> mode="grid" :imageStyles="{ width: '168rpx', height: '168rpx' }" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="商品名称" name="name" required> <uni-forms-item label="商品名称" name="name" required>
<uni-easyinput type="text" trim="all" v-model="formData.name" placeholder="请输入商品名称" /> <uni-easyinput type="text" trim="all" v-model="formData.name" :disabled="!canEdit" placeholder="请输入商品名称" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="商品分类" @tap="openPicker('category', 'multiple')" name="categoryId" label-position="left" <uni-forms-item label="商品分类" @tap="openPicker('category', 'multiple')" name="categoryId" label-position="left"
required> required>
<uni-easyinput type="text" v-model="formData.categoryText" :styles="selfStyles" <uni-easyinput type="text" v-model="formData.categoryText" :disabled="!canEdit" :styles="selfStyles"
placeholderStyle="color:#8a8a8a" :clearable="false" :inputBorder="false" placeholder="请选择商品分类" disabled> placeholderStyle="color:#8a8a8a" :clearable="false" :inputBorder="false" placeholder="请选择商品分类" disabled>
<template v-slot:right> <template v-slot:right>
<uni-icons type="right" /> <uni-icons type="right" />
@ -24,8 +24,8 @@
</uni-easyinput> </uni-easyinput>
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="商品品牌" name="brandId" label-position="left" required @tap="openPicker('brand', 'single')"> <uni-forms-item label="商品品牌" name="brandId" label-position="left" required @tap="openPicker('brand', 'single')">
<uni-easyinput type="text" v-model="formData.brandText" :styles="selfStyles" placeholderStyle="color:#8a8a8a" <uni-easyinput type="text" v-model="formData.brandText" :disabled="!canEdit" :styles="selfStyles"
:clearable="false" :inputBorder="false" placeholder="请选择商品品牌" disabled> placeholderStyle="color:#8a8a8a" :clearable="false" :inputBorder="false" placeholder="请选择商品品牌" disabled>
<template v-slot:right> <template v-slot:right>
<uni-icons type="right" /> <uni-icons type="right" />
</template> </template>
@ -37,21 +37,25 @@
</view> </view>
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="商品关键词" name="keyword" required> <uni-forms-item label="商品关键词" name="keyword" required>
<uni-easyinput type="text" v-model="formData.keyword" placeholder="请输入商品关键词" /> <uni-easyinput type="text" v-model="formData.keyword" :disabled="!canEdit" placeholder="请输入商品关键词" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="商品简介" name="introduction" required> <uni-forms-item label="商品简介" name="introduction" required>
<uni-easyinput type="textarea" trim="all" autoHeight v-model="formData.introduction" placeholder="请输入商品简介" /> <uni-easyinput type="textarea" :disabled="!canEdit" trim="all" autoHeight v-model="formData.introduction"
placeholder="请输入商品简介" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="物流设置" @tap="openPicker('delivery', 'single')" name="deliveryTypes" label-position="left" <uni-forms-item label="物流设置" @tap="openPicker('delivery', 'single')" name="deliveryTypes" label-position="left"
required> required>
<uni-easyinput type="text" :clearable="false" :styles="selfStyles" placeholderStyle="color:#8a8a8a" <uni-easyinput type="text" :clearable="false" :styles="selfStyles" placeholderStyle="color:#8a8a8a"
:inputBorder="false" v-model="formData.deliveryText" placeholder="请选择配送方式" disabled> :inputBorder="false" v-model="formData.deliveryText" :disabled="!canEdit" placeholder="请选择配送方式" disabled>
<template v-slot:right> <template v-slot:right>
<uni-icons type="right" /> <uni-icons type="right" />
</template> </template>
</uni-easyinput> </uni-easyinput>
</uni-forms-item> </uni-forms-item>
</uni-forms> </uni-forms>
<view @tap="onSubmit" v-if="canEdit">
<button class="ss-reset-button submit-button ui-Shadow-Main">提交</button>
</view>
</view> </view>
<p-picker ref="pickerRef" :mode="pickerMode" :options-cols="optionsCols" @confirm="onRDPickerConfirm"></p-picker> <p-picker ref="pickerRef" :mode="pickerMode" :options-cols="optionsCols" @confirm="onRDPickerConfirm"></p-picker>
@ -59,7 +63,7 @@
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app' import { onLoad } from '@dcloudio/uni-app'
import peach from '@/peach' import peach from '@/peach'
import { handleTree } from '@/peach/utils' import { handleTree } from '@/peach/utils'
@ -183,11 +187,14 @@ const rules = {
const formRef = ref(null) const formRef = ref(null)
const pickerMode = ref('single') const pickerMode = ref('single')
const popMark = ref('') const popMark = ref('')
const goodsTitle = ref('')
const categoryList = ref([]) const categoryList = ref([])
const brandList = ref([]) const brandList = ref([])
const optionsCols = ref([]) const optionsCols = ref([])
const canEdit = computed(() => peach.$store('trade').canEdit)
function openPicker(mark, mode) { function openPicker(mark, mode) {
if (!canEdit.value) return
pickerMode.value = mode pickerMode.value = mode
popMark.value = mark popMark.value = mark
if (mark === 'delivery') { if (mark === 'delivery') {
@ -290,12 +297,12 @@ function onSubmit() {
.then(async (res) => { .then(async (res) => {
let tempObj = { ...res } let tempObj = { ...res }
if (formData.value.id) { // if (formData.value.id) {
tempObj.id = formData.value.id // tempObj.id = formData.value.id
await GoodsApi.editProduct(tempObj) // await GoodsApi.editProduct(tempObj)
} else { // } else {
await GoodsApi.addProduct(tempObj) // await GoodsApi.addProduct(tempObj)
} // }
}) })
.catch((err) => { .catch((err) => {
console.log('err', err) console.log('err', err)
@ -317,13 +324,28 @@ async function getBrandList() {
onLoad(async (options) => { onLoad(async (options) => {
await getCategoryList() await getCategoryList()
await getBrandList() await getBrandList()
goodsTitle.value = options.title
if (options.id) { if (options.id) {
getProduct(options.id) getProduct(options.id)
peach.$store('trade').detailTag = options.mark
} }
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@mixin ss-set-property {
width: 80px;
height: 60rpx;
line-height: normal;
background: var(--ui-BG-Main);
border-radius: 28rpx;
font-size: 26rpx;
font-weight: 500;
color: #fff;
}
.manage-goods { .manage-goods {
.goods-form { .goods-form {
margin: 40rpx; margin: 40rpx;
@ -355,10 +377,18 @@ onLoad(async (options) => {
} }
} }
.is-disabled { .is-direction-left {
color: #333333;
text-align: right; .is-disabled {
color: #333333;
text-align: right;
}
.uni-forms-item__error {
left: -160rpx !important;
}
} }
} }
.btn-group { .btn-group {
@ -370,16 +400,17 @@ onLoad(async (options) => {
border-radius: 0 10px 10px 0; border-radius: 0 10px 10px 0;
.ss-set-property { .ss-set-property {
width: 80px; @include ss-set-property
height: 60rpx;
line-height: normal;
background: var(--ui-BG-Main);
border-radius: 28rpx;
font-size: 26rpx;
font-weight: 500;
color: #fff;
} }
} }
.submit-button {
width: 100%;
height: 80rpx;
border-radius: 40rpx;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
color: $white;
}
} }
} }
</style> </style>

View File

@ -1,57 +1,45 @@
<template> <template>
<pb-layout <pb-layout class="goods-property" title="商品属性" leftIcon="leftIcon" navbar="normal" :bgStyle="bgStyle"
class="goods-property" opacityBgUi="bg-white" color="black">
title="商品属性" <view class="property">
leftIcon="leftIcon" <uni-forms ref="formRef" v-model="formData" :rules="rules" label-position="top" label-width="160">
navbar="normal" <uni-forms-item label="商品规格" @tap="pickerProperty" name="specType" label-position="left" required>
:bgStyle="bgStyle" <uni-easyinput type="text" :clearable="false" :styles="selfStyles" placeholderStyle="color:#8a8a8a"
opacityBgUi="bg-white" :inputBorder="false" v-model="formData.specText" placeholder="请选择商品规格" disabled>
color="black" <template v-slot:right>
> <uni-icons type="right" />
<view class="property"> </template>
<uni-forms ref="formRef" v-model="formData" :rules="rules" label-position="top" label-width="160"> </uni-easyinput>
<uni-forms-item label="商品规格" @tap="pickerProperty" name="specType" label-position="left" required> </uni-forms-item>
<uni-easyinput </uni-forms>
type="text" </view>
:clearable="false" <template v-if="formData.specType">
:styles="selfStyles" <!-- 添加商品 -->
placeholderStyle="color:#8a8a8a" <button v-if="canEdit" class="ss-reset-button add-property" @tap="showPropertyList">+添加规格</button>
:inputBorder="false" <!-- 商品属性展示 -->
v-model="formData.specText" <property-detail v-if="propertyList.length > 0" v-model="propertyList" :goodsPropertyList="goodsPropertyList"
placeholder="请选择商品规格" @changeSubProperty="changeSubProperty"></property-detail>
disabled <!-- 多规格商品 -->
> <mutiple-sku :skus="skus"></mutiple-sku>
<template v-slot:right> </template>
<uni-icons type="right" />
</template>
</uni-easyinput>
</uni-forms-item>
</uni-forms>
</view>
<template v-if="formData.specType">
<button class="ss-reset-button add-property" @tap="showPropertyList">+添加规格</button>
<property-detail
v-if="propertyList.length > 0"
v-model="propertyList"
:goodsPropertyList="goodsPropertyList"
@changeSubProperty="changeSubProperty"
></property-detail>
<mutiple-sku :skus="skus"></mutiple-sku>
</template>
<template v-else> <template v-else>
<SkuItem :skus="skus" /> <!-- 单规格商品 -->
</template> <SkuItem :skus="skus" />
</template>
<p-picker ref="pickerRef" mode="single" :options-cols="SPEC_TYPE" @confirm="onRDPickerConfirm"></p-picker> <!-- 确认选择 -->
<view style="padding: 0 40rpx 40rpx;" @tap="onConfirm" v-if="canEdit">
<button class="ss-reset-button submit-button ui-Shadow-Main">提交</button>
</view>
<PropertyList <!-- 商品规格 -->
ref="propertyListRef" <p-picker ref="pickerRef" mode="single" :options-cols="SPEC_TYPE" @confirm="onRDPickerConfirm"></p-picker>
v-model="propertyList"
:goodsPropertyList="goodsPropertyList" <!-- 商品属性列表 -->
@confirm="onPropertyConfirm" <PropertyList ref="propertyListRef" v-model="propertyList" :goodsPropertyList="goodsPropertyList"
/> @confirm="onPropertyConfirm" />
</pb-layout> </pb-layout>
</template> </template>
<script setup> <script setup>
@ -61,24 +49,25 @@ import PropertyList from './components/propertyList'
import PropertyDetail from './components/propertyDetail' import PropertyDetail from './components/propertyDetail'
import { SPEC_TYPE } from './js/config' import { SPEC_TYPE } from './js/config'
import { import {
initial, initial,
pickerRef, canEdit,
propertyListRef, pickerRef,
onRDPickerConfirm, propertyListRef,
formData, onRDPickerConfirm,
propertyList, formData,
onPropertyConfirm, propertyList,
showPropertyList, onPropertyConfirm,
goodsPropertyList, showPropertyList,
pickerProperty, goodsPropertyList,
skus, pickerProperty,
changeSubProperty, skus,
changeSubProperty,
} from './js/sku' } from './js/sku'
const bgStyle = { const bgStyle = {
backgroundImage: '', backgroundImage: '',
backgroundColor: '#fff', backgroundColor: '#fff',
description: '', description: '',
} }
initial() initial()
@ -86,49 +75,57 @@ initial()
<style lang="scss" scoped> <style lang="scss" scoped>
.goods-property { .goods-property {
.property { .property {
margin: 40rpx; margin: 40rpx;
:deep() { :deep() {
.uni-easyinput__content-input { .uni-easyinput__content-input {
font-size: 28rpx !important; font-size: 28rpx !important;
color: #333333 !important; color: #333333 !important;
line-height: normal !important; line-height: normal !important;
} }
.uni-easyinput__placeholder-class { .uni-easyinput__placeholder-class {
font-size: 14px; font-size: 14px;
} }
.is-direction-left { .is-direction-left {
.uni-forms-item__label { .uni-forms-item__label {
padding-left: 10px; padding-left: 10px;
background-color: #f9f9f9; background-color: #f9f9f9;
border-radius: 10px 0 0 10px; border-radius: 10px 0 0 10px;
}
uni-icons {
margin-right: 10px;
}
.uni-easyinput__content {
border-radius: 0 10px 10px 0;
}
}
.is-disabled {
color: #333333;
text-align: right;
}
} }
}
.add-property { uni-icons {
margin: 40rpx; margin-right: 10px;
border: 1px dotted var(--ui-BG-Main); }
color: var(--ui-BG-Main);
border-radius: 10px; .uni-easyinput__content {
text-align: center; border-radius: 0 10px 10px 0;
}
}
.is-disabled {
color: #333333;
text-align: right;
}
} }
}
.add-property {
margin: 40rpx;
border: 1px dotted var(--ui-BG-Main);
color: var(--ui-BG-Main);
border-radius: 10px;
text-align: center;
}
.submit-button {
width: 100%;
height: 80rpx;
border-radius: 40rpx;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
color: $white;
}
} }
</style> </style>

View File

@ -1,58 +1,46 @@
<template> <template>
<view class="ss-goods-wrap"> <view class="ss-goods-wrap">
<view v-if="size === 'lg'" class="lg-goods-card ss-flex ss-col-stretch" :style="[elStyles]" @click="onClick"> <view v-if="size === 'lg'" class="lg-goods-card ss-flex ss-col-stretch" :style="[elStyles]" @click="onClick">
<image class="lg-img-box" :src="peach.$url.cdn(data.image || data.picUrl)" mode="aspectFill"></image> <image class="lg-img-box" :src="peach.$url.cdn(data.image || data.picUrl)" mode="aspectFill"></image>
<view class="lg-goods-content ss-flex-1 ss-flex-col ss-row-between ss-p-b-10 ss-p-t-20"> <view class="lg-goods-content ss-flex-1 ss-flex-col ss-row-between ss-p-b-10 ss-p-t-20">
<view> <view>
<view <view v-if="goodsFields.title?.show || goodsFields.name?.show" class="lg-goods-title ss-line-2"
v-if="goodsFields.title?.show || goodsFields.name?.show" :style="[{ color: titleColor }]">
class="lg-goods-title ss-line-2" {{ data.title || data.name }}
:style="[{ color: titleColor }]" </view>
> <view v-if="goodsFields.subtitle?.show || goodsFields.introduction?.show"
{{ data.title || data.name }} class="lg-goods-subtitle ss-m-t-10 ss-line-1"
</view> :style="[{ color: subTitleColor, background: subTitleBackground }]">
<view {{ data.subtitle || data.introduction }}
v-if="goodsFields.subtitle?.show || goodsFields.introduction?.show" </view>
class="lg-goods-subtitle ss-m-t-10 ss-line-1" </view>
:style="[{ color: subTitleColor, background: subTitleBackground }]" <view>
> <view class="ss-flex ss-col-bottom ss-m-t-10">
{{ data.subtitle || data.introduction }} <view v-if="goodsFields.price?.show" class="lg-goods-price ss-m-r-12 ss-flex ss-col-bottom font-OPPOSANS"
</view> :style="[{ color: goodsFields.price.color }]">
</view> <text class="ss-font-24">{{ priceUnit }}</text>
<view> {{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
<view class="ss-flex ss-col-bottom ss-m-t-10">
<view
v-if="goodsFields.price?.show"
class="lg-goods-price ss-m-r-12 ss-flex ss-col-bottom font-OPPOSANS"
:style="[{ color: goodsFields.price.color }]"
>
<text class="ss-font-24">{{ priceUnit }}</text>
{{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
</view>
<view
v-if="
(goodsFields.original_price?.show || goodsFields.marketPrice?.show) &&
(data.original_price > 0 || data.marketPrice > 0)
"
class="goods-origin-price ss-flex ss-col-bottom font-OPPOSANS"
:style="[{ color: originPriceColor }]"
>
<text class="price-unit ss-font-20">{{ priceUnit }}</text>
<view class="ss-m-l-8">{{ fen2yuan(data.marketPrice) }}</view>
</view>
</view>
<view class="ss-m-t-8 ss-flex ss-col-center ss-flex-wrap">
<view class="sales-text">{{ salesAndStock }}</view>
</view>
</view>
</view> </view>
<view v-if="
(goodsFields.original_price?.show || goodsFields.marketPrice?.show) &&
(data.original_price > 0 || data.marketPrice > 0)
" class="goods-origin-price ss-flex ss-col-bottom font-OPPOSANS" :style="[{ color: originPriceColor }]">
<text class="price-unit ss-font-20">{{ priceUnit }}</text>
<view class="ss-m-l-8">{{ fen2yuan(data.marketPrice) }}</view>
</view>
</view>
<view class="ss-m-t-8 ss-flex ss-col-center ss-flex-wrap">
<view class="sales-text">{{ salesAndStock }}</view>
</view>
</view> </view>
<view class="ss-flex ss-row-around" :style="btnStyles"> </view>
<button class="ss-reset-button btn-group" @click="clickGoods('detail')">详情</button>
<button class="ss-reset-button btn-group" @click="clickGoods('edit')">编辑</button>
<button class="ss-reset-button btn-group btn-del" @click="clickGoods('del')">删除</button>
</view>
</view> </view>
<view class="ss-flex ss-row-around" :style="btnStyles">
<button class="ss-reset-button btn-group" @click="clickGoods('detail')">详情</button>
<button class="ss-reset-button btn-group" @click="clickGoods('edit')">编辑</button>
<button class="ss-reset-button btn-group btn-del" @click="clickGoods('del')">删除</button>
</view>
</view>
</template> </template>
<script setup> <script setup>
@ -63,167 +51,171 @@ import { fen2yuan, formatSales, formatStock } from '@/peach/hooks/useGoods'
import { unix } from 'dayjs' import { unix } from 'dayjs'
const props = defineProps({ const props = defineProps({
goodsFields: { goodsFields: {
type: [Array, Object], type: [Array, Object],
default() { default() {
return { return {
price: { show: true }, price: { show: true },
stock: { show: true }, stock: { show: true },
name: { show: true }, name: { show: true },
introduction: { show: true }, introduction: { show: true },
marketPrice: { show: true }, marketPrice: { show: true },
salesCount: { show: true }, salesCount: { show: true },
} }
},
},
data: {
type: Object,
default: {},
},
size: {
type: String,
default: '',
},
originPriceColor: {
type: String,
default: '#C4C4C4',
},
topRadius: {
type: Number,
default: 0,
},
bottomRadius: {
type: Number,
default: 0,
},
priceUnit: {
type: String,
default: '¥',
},
titleColor: {
type: String,
default: '#333',
},
subTitleColor: {
type: String,
default: '#999999',
},
subTitleBackground: {
type: String,
default: '',
}, },
},
data: {
type: Object,
default: {},
},
size: {
type: String,
default: '',
},
originPriceColor: {
type: String,
default: '#C4C4C4',
},
topRadius: {
type: Number,
default: 0,
},
bottomRadius: {
type: Number,
default: 0,
},
priceUnit: {
type: String,
default: '¥',
},
titleColor: {
type: String,
default: '#333',
},
subTitleColor: {
type: String,
default: '#999999',
},
subTitleBackground: {
type: String,
default: '',
},
}) })
const emits = defineEmits(['click']) const emits = defineEmits(['click'])
function onClick() { function onClick() {
emits('click') emits('click')
} }
const elStyles = computed(() => { const elStyles = computed(() => {
return { return {
background: props.background, background: props.background,
'border-top-left-radius': props.topRadius + 'px', 'border-top-left-radius': props.topRadius + 'px',
'border-top-right-radius': props.topRadius + 'px', 'border-top-right-radius': props.topRadius + 'px',
} }
}) })
const btnStyles = computed(() => { const btnStyles = computed(() => {
return { return {
background: '#fff', background: '#fff',
'border-bottom-left-radius': props.bottomRadius + 'px', 'border-bottom-left-radius': props.bottomRadius + 'px',
'border-bottom-right-radius': props.bottomRadius + 'px', 'border-bottom-right-radius': props.bottomRadius + 'px',
padding: '8px 0', padding: '8px 0',
} }
}) })
// //
const salesAndStock = computed(() => { const salesAndStock = computed(() => {
let text = [] let text = []
if (props.goodsFields.salesCount?.show) { if (props.goodsFields.salesCount?.show) {
text.push(formatSales(props.data.sales_show_type, props.data.salesCount)) text.push(formatSales(props.data.sales_show_type, props.data.salesCount))
} }
if (props.goodsFields.stock?.show) { if (props.goodsFields.stock?.show) {
text.push(formatStock(props.data.stock_show_type, props.data.stock)) text.push(formatStock(props.data.stock_show_type, props.data.stock))
} }
return text.join(' | ') return text.join(' | ')
}) })
function clickGoods(mark) { function clickGoods(mark) {
if (mark === 'detail' || mark === 'edit') {
peach.$router.go('/pages/product/manageGoods', { if (mark === 'detail' || mark === 'edit') {
id: props.data.id, peach.$router.go('/pages/product/manageGoods', {
mark: mark, id: props.data.id,
}) mark: mark,
} else if (mark === 'del') { title: mark === 'detail' ? '商品详情' : '编辑商品'
uni.showModal({ })
title: '提示', } else if (mark === 'del') {
content: '是否删除该商品?', uni.showModal({
success: (res) => { title: '提示',
if (res.confirm) { content: '是否删除该商品?',
} success: (res) => {
}, if (res.confirm) {
}) }
} },
})
}
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.ss-goods-wrap { .ss-goods-wrap {
.lg-goods-card { .lg-goods-card {
overflow: hidden; overflow: hidden;
position: relative; position: relative;
z-index: 1; z-index: 1;
background-color: $white; background-color: $white;
height: 280rpx; height: 280rpx;
.lg-img-box { .lg-img-box {
width: 280rpx; width: 280rpx;
height: 280rpx; height: 280rpx;
margin-right: 20rpx; margin-right: 20rpx;
}
.lg-goods-title {
font-size: 28rpx;
font-weight: 500;
color: #333333;
// line-height: 36rpx;
// width: 410rpx;
}
.lg-goods-subtitle {
font-size: 24rpx;
font-weight: 400;
color: #999999;
// line-height: 30rpx;
// width: 410rpx;
}
.lg-goods-price {
font-size: 30rpx;
color: $red;
line-height: 36rpx;
}
.sales-text {
display: table;
font-size: 24rpx;
transform: scale(0.8);
margin-left: 0rpx;
color: #c4c4c4;
}
} }
.btn-group {
width: 120rpx; .lg-goods-title {
height: 50rpx; font-size: 28rpx;
background: var(--ui-BG-1); font-weight: 500;
border-radius: 25rpx; color: #333333;
font-size: 24rpx; // line-height: 36rpx;
color: #000; // width: 410rpx;
} }
.btn-del {
color: var(--ui-BG-Main); .lg-goods-subtitle {
background-color: var(--ui-BG-Main-opacity-1); font-size: 24rpx;
font-weight: 400;
color: #999999;
// line-height: 30rpx;
// width: 410rpx;
} }
.lg-goods-price {
font-size: 30rpx;
color: $red;
line-height: 36rpx;
}
.sales-text {
display: table;
font-size: 24rpx;
transform: scale(0.8);
margin-left: 0rpx;
color: #c4c4c4;
}
}
.btn-group {
width: 120rpx;
height: 50rpx;
background: var(--ui-BG-1);
border-radius: 25rpx;
font-size: 24rpx;
color: #000;
}
.btn-del {
color: var(--ui-BG-Main);
background-color: var(--ui-BG-Main-opacity-1);
}
} }
</style> </style>

View File

@ -1,47 +1,35 @@
<template> <template>
<view class="custom-picker"> <view class="custom-picker">
<!-- 普通弹窗 --> <!-- 普通弹窗 -->
<uni-popup type="bottom" ref="pickerPopupRef" background-color="#fff"> <uni-popup type="bottom" ref="pickerPopupRef" background-color="#fff">
<view class="popup-header"> <view class="popup-header">
<view class="button-cancel" @click="onClosePopup">取消</view> <view class="button-cancel" @click="onClosePopup">取消</view>
<view class="button-link" @click="onConfirmPopup">确定</view> <view class="button-link" @click="onConfirmPopup">确定</view>
</view> </view>
<view class="popup-content"> <view class="popup-content">
<picker-view <picker-view :indicator-style="indicatorStyle" :value="pickerViewValue" @change="bindChange"
:indicator-style="indicatorStyle" class="picker-view">
:value="pickerViewValue" <template v-if="mode === 'single'">
@change="bindChange" <picker-view-column>
class="picker-view" <view class="item" v-for="(item, index) in props.optionsCols" :key="`${item.value}-${index}`">{{
> item.label ?? item.name }}</view>
<template v-if="mode === 'single'"> </picker-view-column>
<picker-view-column> </template>
<view <template v-else>
class="item" <picker-view-column>
v-for="(item, index) in props.optionsCols" <view class="item" v-for="(item, index) in props.optionsCols" :key="`${item.value}-${index}`">{{ item.name
:key="`${item.value}-${index}`" }}</view>
>{{ item.label }}</view </picker-view-column>
> <picker-view-column>
</picker-view-column> <view class="item" v-for="(item, index) in childrenList" :key="`${item.value}-${index}`">{{
</template> item.name
<template v-else> }}</view>
<picker-view-column> </picker-view-column>
<view </template>
class="item" </picker-view>
v-for="(item, index) in props.optionsCols" </view>
:key="`${item.value}-${index}`" </uni-popup>
>{{ item.name }}</view </view>
>
</picker-view-column>
<picker-view-column>
<view class="item" v-for="(item, index) in childrenList" :key="`${item.value}-${index}`">{{
item.name
}}</view>
</picker-view-column>
</template>
</picker-view>
</view>
</uni-popup>
</view>
</template> </template>
<script setup> <script setup>
@ -49,20 +37,20 @@ import { defineEmits, defineProps, ref, onMounted, computed, defineExpose } from
const pickerViewValue = ref([]) const pickerViewValue = ref([])
const indicatorStyle = `height: 50px` const indicatorStyle = `height: 50px`
const props = defineProps({ const props = defineProps({
// pickerview value label // pickerview value label
optionsCols: { optionsCols: {
default: () => [], default: () => [],
required: true, required: true,
type: Array, type: Array,
}, },
mode: { mode: {
type: String, type: String,
default: 'single', default: 'single',
}, },
}) })
const childrenList = computed(() => { const childrenList = computed(() => {
return props.optionsCols[pickerViewValue.value[0]]?.children return props.optionsCols[pickerViewValue.value[0]]?.children
}) })
// confirm change // confirm change
@ -76,8 +64,8 @@ const pickerPopupRef = ref(null)
* @return {*} * @return {*}
*/ */
const onOpen = (defaultValue) => { const onOpen = (defaultValue) => {
pickerViewValue.value = defaultValue pickerViewValue.value = defaultValue
pickerPopupRef.value.open('bottom') pickerPopupRef.value.open('bottom')
} }
/** /**
* @author: joey wong * @author: joey wong
@ -86,17 +74,17 @@ const onOpen = (defaultValue) => {
* @return {*} * @return {*}
*/ */
const bindChange = (e) => { const bindChange = (e) => {
pickerViewValue.value = e.detail.value pickerViewValue.value = e.detail.value
if (props.mode === 'multiple') { if (props.mode === 'multiple') {
if (pickerViewValue.value[0] !== e.detail.value[0]) { if (pickerViewValue.value[0] !== e.detail.value[0]) {
pickerViewValue.value[1] = 0 pickerViewValue.value[1] = 0
}
} }
}
emit('change', { emit('change', {
value: pickerViewValue.value, value: pickerViewValue.value,
}) })
} }
/** /**
* @author: joey wong * @author: joey wong
@ -104,7 +92,7 @@ const bindChange = (e) => {
* @return {*} * @return {*}
*/ */
const onClosePopup = () => { const onClosePopup = () => {
pickerPopupRef.value.close() pickerPopupRef.value.close()
} }
/** /**
* @author: joey wong * @author: joey wong
@ -112,56 +100,61 @@ const onClosePopup = () => {
* @return {*} * @return {*}
*/ */
const onConfirmPopup = () => { const onConfirmPopup = () => {
emit('confirm', { emit('confirm', {
value: pickerViewValue.value, value: pickerViewValue.value,
}) })
onClosePopup() onClosePopup()
} }
defineExpose({ defineExpose({
onOpen, onOpen,
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.custom-picker { .custom-picker {
display: flex;
align-items: center;
&-text {
margin-right: 5.7rpx;
}
&-icon {
width: 19rpx;
height: 12rpx;
margin-bottom: 3rpx;
}
.popup-content {
height: 500rpx;
.item {
text-align: center;
line-height: 34px;
}
}
.picker-view {
width: 100%;
height: 100%;
margin-top: 20rpx;
}
.button-link {
color: #1892ea;
font-size: 28rpx;
}
.button-cancel {
color: #888;
font-size: 28rpx;
}
.popup-header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between;
&-text { padding: 24rpx 38rpx 0;
margin-right: 5.7rpx; }
}
&-icon {
width: 19rpx;
height: 12rpx;
margin-bottom: 3rpx;
}
.popup-content {
height: 500rpx;
.item {
text-align: center;
line-height: 50px;
}
}
.picker-view {
width: 100%;
height: 100%;
margin-top: 20rpx;
}
.button-link {
color: #1892ea;
font-size: 28rpx;
}
.button-cancel {
color: #888;
font-size: 28rpx;
}
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 38rpx 0;
}
} }
</style> </style>

View File

@ -282,6 +282,7 @@ export default {
return this.uploadFiles(files); return this.uploadFiles(files);
}, },
async setValue(newVal, oldVal) { async setValue(newVal, oldVal) {
const newData = async (v) => { const newData = async (v) => {
const reg = /cloud:\/\/([\w.]+\/?)\S*/; const reg = /cloud:\/\/([\w.]+\/?)\S*/;
let url = ''; let url = '';
@ -512,6 +513,7 @@ export default {
* @param {Object} index * @param {Object} index
*/ */
delFile(index) { delFile(index) {
this.$emit('delete', { this.$emit('delete', {
tempFile: this.files[index], tempFile: this.files[index],
tempFilePath: this.files[index].url, tempFilePath: this.files[index].url,
@ -541,11 +543,13 @@ export default {
setEmit() { setEmit() {
let data = []; let data = [];
let updateUrl = []; let updateUrl = [];
//
if (this.returnType === 'object') { if (this.returnType === 'object') {
data = this.backObject(this.files)[0]; data = this.backObject(this.files)[0];
this.localValue = data ? data : null; this.localValue = data ? data : null;
updateUrl = data ? data.url : ''; updateUrl = data ? data.url : '';
} else { } else {
//
data = this.backObject(this.files); data = this.backObject(this.files);
if (!this.localValue) { if (!this.localValue) {
this.localValue = []; this.localValue = [];
@ -568,16 +572,23 @@ export default {
backObject(files) { backObject(files) {
let newFilesData = []; let newFilesData = [];
files.forEach((v) => { files.forEach((v) => {
newFilesData.push({ if (v.fileID) {
extname: v.extname,
fileType: v.fileType, newFilesData.push({
image: v.image, extname: v.extname,
name: v.name, fileType: v.fileType,
path: v.path, image: v.image,
size: v.size, name: v.name,
fileID: v.fileID, path: v.path,
url: v.url, size: v.size,
}); fileID: v.fileID,
url: v.url,
});
} else {
newFilesData.push({
url: v
})
}
}); });
return newFilesData; return newFilesData;
}, },

View File

@ -69,6 +69,7 @@ export default {
}, },
computed: { computed: {
list() { list() {
if (typeof this.filesList === 'string') { if (typeof this.filesList === 'string') {
if (this.filesList) { if (this.filesList) {
return [this.filesList]; return [this.filesList];

View File

@ -1,4 +1,4 @@
import { ref } from "vue"; import { ref, computed } from "vue";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
const useTradeStore = defineStore("trade", () => { const useTradeStore = defineStore("trade", () => {
@ -8,13 +8,21 @@ const useTradeStore = defineStore("trade", () => {
// 商品信息 // 商品信息
const goodsInfo = ref(null); const goodsInfo = ref(null);
// 详情标记
const detailTag = ref("edit");
// 商品属性 // 商品属性
const skus = ref(null); const skus = ref(null);
// 商品是否可编辑
const canEdit = computed(() => (detailTag.value === "detail" ? false : true));
return { return {
selectedProperty, selectedProperty,
goodsInfo, goodsInfo,
skus, skus,
canEdit,
detailTag,
}; };
}); });