feat(商品管理)

This commit is contained in:
Ankkaya 2024-06-04 18:43:13 +08:00
parent 0a2d2e4601
commit 30f3a4c2c4
10 changed files with 892 additions and 225 deletions

View File

@ -163,6 +163,15 @@
"meta": { "meta": {
"auth": false "auth": false
} }
},
{
"path": "sku",
"style": {
"navigationBarTitleText": "商品属性"
},
"meta": {
"auth": false
}
} }
] ]
}, },

View File

@ -0,0 +1,48 @@
<template>
<div class="sku-item">
<uni-forms label-width="160" label-position="top">
<uni-forms-item label="商品封面图" name="picUrl">
<p-uploader
v-model:url="formData.picUrl"
fileMediatype="image"
limit="1"
mode="grid"
:imageStyles="{ width: '168rpx', height: '168rpx' }"
/>
</uni-forms-item>
<uni-forms-item label="商品条码" name="barCode">
<uni-easyinput type="text" trim="all" v-model="formData.barCode" placeholder="请输入商品条码" />
</uni-forms-item>
<uni-forms-item label="销售价" name="price">
<uni-easyinput type="number" trim="all" v-model="formData.price" placeholder="请输入商品销售价" />
</uni-forms-item>
<uni-forms-item label="市场价" name="marketPrice">
<uni-easyinput type="number" trim="all" v-model="formData.marketPrice" placeholder="请输入商品销售价" />
</uni-forms-item>
<uni-forms-item label="成本价" name="costPrice">
<uni-easyinput type="number" trim="all" v-model="formData.costPrice" placeholder="请输入商品销售价" />
</uni-forms-item>
<uni-forms-item label="库存" name="stock">
<uni-easyinput type="number" trim="all" v-model="formData.stock" placeholder="请输入商品库存" />
</uni-forms-item>
<uni-forms-item label="重量kg" name="weight">
<uni-easyinput type="number" trim="all" v-model="formData.weight" placeholder="请输入商品重量" />
</uni-forms-item>
<uni-forms-item label="体积" name="volume">
<uni-easyinput type="number" trim="all" v-model="formData.volume" placeholder="请输入商品体积" />
</uni-forms-item>
</uni-forms>
</div>
</template>
<script setup>
import { ref } from 'vue'
const formData = ref({})
</script>
<style lang="scss" scoped>
.sku-item {
margin: 40rpx;
}
</style>

View File

@ -0,0 +1,132 @@
<template>
<view class="property-list">
<uni-popup type="bottom" ref="propertyListPopupRef" background-color="#fff">
<view class="popup-header">
<view class="button-cancel" @click="onClosePopup">取消</view>
<view class="button-link" @click="onConfirmPopup">确定</view>
</view>
<view class="popup-content">
<view
v-for="item in propertyList"
:key="item.id"
:class="['property-item', item.checked ? 'active' : '']"
@tap="chooseProperty(item)"
>
{{ item.label }}
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { ref, computed, defineEmits, defineProps, defineExpose } from 'vue'
/**
* todo 底部高度配置
*/
const props = defineProps({
modelValue: {
default: () => [],
required: true,
type: Array,
},
})
const emit = defineEmits(['update:modelValue'])
const propertyList = ref([
{
label: '红色',
id: 1,
checked: false,
},
{
label: '蓝色',
id: 2,
checked: false,
},
{
label: '白色',
id: 3,
checked: false,
},
])
const propertyListPopupRef = ref()
const onClosePopup = () => {
propertyListPopupRef.value.close()
}
function chooseProperty(item) {
const index = props.modelValue.findIndex((v) => v.id === item.id)
item.checked = !item.checked
if (index === -1) {
props.modelValue.push(item)
} else {
props.modelValue.splice(index, 1)
}
}
function onConfirmPopup() {
emit('update:modelValue', propertyList.value.filter((item) => item.checked).map((item) => item.id) ?? [])
onClosePopup()
}
function onOpen() {
propertyList.value.map((item) => {
item.checked = false
return item
})
propertyList.value = propertyList.value.map((item) =>
props.modelValue.some((id) => id === item.id) ? { ...item, checked: true } : item
)
propertyListPopupRef.value.open('bottom')
}
defineExpose({
onOpen,
})
</script>
<style lang="scss" scoped>
.property-list {
.popup-content {
height: 500rpx;
padding: 40rpx;
display: flex;
flex-wrap: wrap;
gap: 20rpx;
justify-content: flex-start;
.property-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);
}
}
.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>

View File

@ -0,0 +1,10 @@
export const SPEC_TYPE = [
{
label: '单规格',
value: false,
},
{
label: '多规格',
value: true,
},
]

24
pages/product/js/sku.js Normal file
View File

@ -0,0 +1,24 @@
import { ref } from 'vue'
import { SPEC_TYPE } from './config'
const pickerRef = ref(null)
const propertyList = ref([1, 2])
const propertyListRef = ref(null)
const formData = ref({
specType: true,
specText: SPEC_TYPE[0].label,
})
function onRDPickerConfirm(e) {
formData.value.specText = SPEC_TYPE[e.value[0]].label
formData.value.specType = SPEC_TYPE[e.value[0]].value
}
function onPropertyConfirm(e) {
console.log(e)
}
export { pickerRef, propertyListRef, formData, onRDPickerConfirm, onPropertyConfirm, propertyList }

View File

@ -1,273 +1,374 @@
<template> <template>
<pb-layout class="manage-goods" title="发布商品" leftIcon="leftIcon" navbar="normal" :bgStyle="bgStyle" <pb-layout
opacityBgUi="bg-white" color="black"> class="manage-goods"
<view class="goods-form"> title="发布商品"
<uni-forms ref="formRef" v-model="formData" :rules="rules" label-position="top" label-width="160"> leftIcon="leftIcon"
<uni-forms-item label="商品封面图" name="picUrl" required> navbar="normal"
<p-uploader v-model:url="formData.picUrl" fileMediatype="image" limit="1" mode="grid" :bgStyle="bgStyle"
:imageStyles="{ width: '168rpx', height: '168rpx' }" /> opacityBgUi="bg-white"
</uni-forms-item> color="black"
<uni-forms-item label="商品轮播图" name="sliderPicUrls" required> >
<p-uploader v-model:url="formData.sliderPicUrls" fileMediatype="image" limit="6" mode="grid" <view class="goods-form">
:imageStyles="{ width: '168rpx', height: '168rpx' }" /> <uni-forms ref="formRef" v-model="formData" :rules="rules" label-position="top" label-width="160">
</uni-forms-item> <uni-forms-item label="商品封面图" name="picUrl" required>
<uni-forms-item label="商品名称" name="name" required> <p-uploader
<uni-easyinput type="text" trim="all" v-model="formData.name" placeholder="请输入商品名称" /> v-model:url="formData.picUrl"
</uni-forms-item> fileMediatype="image"
<uni-forms-item label="商品分类" name="categoryId" label-position="left" required> limit="1"
<uni-easyinput type="text" v-model="formData.categoryId" :styles="selfStyles" placeholderStyle="color:#8a8a8a" mode="grid"
:clearable="false" :inputBorder="false" placeholder="请选择商品分类" disabled> :imageStyles="{ width: '168rpx', height: '168rpx' }"
<template v-slot:right> />
<uni-icons type="right" /> </uni-forms-item>
</template> <uni-forms-item label="商品轮播图" name="sliderPicUrls" required>
</uni-easyinput> <p-uploader
</uni-forms-item> v-model:url="formData.sliderPicUrls"
<uni-forms-item label="商品品牌" name="brandId" label-position="left" required> fileMediatype="image"
<uni-easyinput type="text" v-model="formData.brandId" :styles="selfStyles" placeholderStyle="color:#8a8a8a" limit="6"
:clearable="false" :inputBorder="false" placeholder="请选择商品品牌" disabled> mode="grid"
<template v-slot:right> :imageStyles="{ width: '168rpx', height: '168rpx' }"
<uni-icons type="right" /> />
</template> </uni-forms-item>
</uni-easyinput> <uni-forms-item label="商品名称" name="name" required>
</uni-forms-item> <uni-easyinput type="text" trim="all" v-model="formData.name" placeholder="请输入商品名称" />
<uni-forms-item label="商品规格" name="skus" label-position="left" required> </uni-forms-item>
<uni-easyinput type="text" v-model="formData.skus" :styles="selfStyles" placeholderStyle="color:#8a8a8a" <uni-forms-item
:clearable="false" :inputBorder="false" placeholder="请添加商品规格" disabled> label="商品分类"
<template v-slot:right> @tap="openPicker('category', 'multiple')"
<uni-icons type="right" /> </template></uni-easyinput> name="categoryId"
</uni-forms-item> label-position="left"
<uni-forms-item label="商品关键词" name="keyword" required> required
<uni-easyinput type="text" v-model="formData.keyword" placeholder="请输入商品关键词" /> >
</uni-forms-item> <uni-easyinput
<uni-forms-item label="商品简介" name="introduction" required> type="text"
<uni-easyinput type="textarea" trim="all" autoHeight v-model="formData.introduction" placeholder="请输入商品简介" /> v-model="formData.categoryText"
</uni-forms-item> :styles="selfStyles"
<uni-forms-item label="物流设置" @click="openPicker" name="deliveryTypes" label-position="left" required> placeholderStyle="color:#8a8a8a"
<uni-easyinput type="text" :clearable="false" :styles="selfStyles" placeholderStyle="color:#8a8a8a" :clearable="false"
:inputBorder="false" v-model="formData.keyword" placeholder="请选择配送方式" disabled> :inputBorder="false"
<template v-slot:right> placeholder="请选择商品分类"
<uni-icons type="right" /> disabled
</template> >
</uni-easyinput> <template v-slot:right>
</uni-forms-item> <uni-icons type="right" />
</uni-forms> </template>
</view> </uni-easyinput>
</uni-forms-item>
<uni-forms-item label="商品品牌" name="brandId" label-position="left" required>
<uni-easyinput
type="text"
v-model="formData.brandId"
:styles="selfStyles"
placeholderStyle="color:#8a8a8a"
:clearable="false"
:inputBorder="false"
placeholder="请选择商品品牌"
disabled
>
<template v-slot:right>
<uni-icons type="right" />
</template>
</uni-easyinput>
</uni-forms-item>
<uni-forms-item label="商品规格" name="skus" label-position="left" required>
<uni-easyinput
type="text"
v-model="formData.skus"
:styles="selfStyles"
placeholderStyle="color:#8a8a8a"
:clearable="false"
:inputBorder="false"
placeholder="请添加商品规格"
disabled
>
<template v-slot:right> <uni-icons type="right" /> </template
></uni-easyinput>
</uni-forms-item>
<uni-forms-item label="商品关键词" name="keyword" required>
<uni-easyinput type="text" v-model="formData.keyword" placeholder="请输入商品关键词" />
</uni-forms-item>
<uni-forms-item label="商品简介" name="introduction" required>
<uni-easyinput
type="textarea"
trim="all"
autoHeight
v-model="formData.introduction"
placeholder="请输入商品简介"
/>
</uni-forms-item>
<uni-forms-item
label="物流设置"
@tap="openPicker('delivery', 'single')"
name="deliveryTypes"
label-position="left"
required
>
<uni-easyinput
type="text"
:clearable="false"
:styles="selfStyles"
placeholderStyle="color:#8a8a8a"
:inputBorder="false"
v-model="formData.deliveryText"
placeholder="请选择配送方式"
disabled
>
<template v-slot:right>
<uni-icons type="right" />
</template>
</uni-easyinput>
</uni-forms-item>
</uni-forms>
</view>
<p-picker
<uni-popup ref="popupRef" type="bottom" border-radius="10px 10px 0 0"> ref="pickerRef"
<picker-view v-if="visible" :indicator-style="indicatorStyle" :value="value" @change="bindChange" :mode="pickerMode"
class="picker-view"> :options-cols="optionsCols"
<picker-view-column> @confirm="onRDPickerConfirm"
<view class="item" v-for="(item, index) in DELIVERY_TYPES" :key="index">{{ item.label }}</view> ></p-picker>
</picker-view-column> </pb-layout>
</picker-view>
</uni-popup>
</pb-layout>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref } 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 GoodsApi from '@/peach/api/trade/goods' import GoodsApi from '@/peach/api/trade/goods'
import _ from 'lodash' import _ from 'lodash'
const bgStyle = { const bgStyle = {
backgroundImage: '', backgroundImage: '',
backgroundColor: '#fff', backgroundColor: '#fff',
description: '', description: '',
} }
const DELIVERY_TYPES = [{ const DELIVERY_TYPES = [
value: 3, {
label: '到店核销' value: 3,
}, { label: '到店核销',
value: 4, },
label: '商家配送' {
}] value: 4,
label: '商家配送',
const indicatorStyle = 'height: 50px' },
]
const selfStyles = { const selfStyles = {
backgroundColor: '#f9f9f9', backgroundColor: '#f9f9f9',
} }
const popupRef = ref() const pickerRef = ref()
const formData = ref({ const formData = ref({
picUrl: 'http://101.43.181.163:9001/mall-backend/8f11e372520501531d06bfce15ea97bbecead41c5e4a36d15d7e40af85729ff3.png', picUrl: 'http://101.43.181.163:9001/mall-backend/8f11e372520501531d06bfce15ea97bbecead41c5e4a36d15d7e40af85729ff3.png',
sliderPicUrls: [ sliderPicUrls: [
'http://101.43.181.163:9001/mall-backend/8f11e372520501531d06bfce15ea97bbecead41c5e4a36d15d7e40af85729ff3.png', 'http://101.43.181.163:9001/mall-backend/8f11e372520501531d06bfce15ea97bbecead41c5e4a36d15d7e40af85729ff3.png',
'http://101.43.181.163:9001/mall-backend/8f11e372520501531d06bfce15ea97bbecead41c5e4a36d15d7e40af85729ff3.png', 'http://101.43.181.163:9001/mall-backend/8f11e372520501531d06bfce15ea97bbecead41c5e4a36d15d7e40af85729ff3.png',
'http://101.43.181.163:9001/mall-backend/8f11e372520501531d06bfce15ea97bbecead41c5e4a36d15d7e40af85729ff3.png', 'http://101.43.181.163:9001/mall-backend/8f11e372520501531d06bfce15ea97bbecead41c5e4a36d15d7e40af85729ff3.png',
], ],
name: '测试商品', name: '测试商品',
categoryId: 91, categoryId: [],
brandId: 4, categoryText: '',
keyword: '香酥鸭,但家', brandId: '',
deliveryTypes: [3], keyword: '香酥鸭,但家',
introduction: '但家贵阳香酥鸭现榨香酥鸭无任何添加剂香酥鸭但家贵阳香酥鸭现榨香酥鸭无任何添加剂香酥鸭', deliveryTypes: [],
deliveryText: '',
introduction: '但家贵阳香酥鸭现榨香酥鸭无任何添加剂香酥鸭但家贵阳香酥鸭现榨香酥鸭无任何添加剂香酥鸭',
}) })
const rules = { const rules = {
name: { name: {
rules: [ rules: [
{ {
required: true, required: true,
errorMessage: '请输入商品名称', errorMessage: '请输入商品名称',
}, },
], ],
}, },
picUrl: { picUrl: {
rules: [ rules: [
{ {
required: true, required: true,
errorMessage: '请上传商品封面图', errorMessage: '请上传商品封面图',
}, },
], ],
}, },
sliderPicUrls: { sliderPicUrls: {
rules: [ rules: [
{ {
required: true, required: true,
errorMessage: '请上传商品轮播图', errorMessage: '请上传商品轮播图',
}, },
], ],
}, },
categoryId: { categoryId: {
rules: [ rules: [
{ {
required: true, required: true,
errorMessage: '请选择商品分类', errorMessage: '请选择商品分类',
}, },
], ],
}, },
brandId: { brandId: {
rules: [ rules: [
{ {
required: true, required: true,
errorMessage: '请选择商品品牌', errorMessage: '请选择商品品牌',
}, },
], ],
}, },
skus: { skus: {
rules: [ rules: [
{ {
required: true, required: true,
errorMessage: '请选择商品规格', errorMessage: '请选择商品规格',
}, },
], ],
}, },
keyword: { keyword: {
rules: [ rules: [
{ {
required: true, required: true,
errorMessage: '请输入商品关键字', errorMessage: '请输入商品关键字',
}, },
], ],
}, },
introduction: { introduction: {
rules: [ rules: [
{ {
required: true, required: true,
errorMessage: '请输入商品简介', errorMessage: '请输入商品简介',
}, },
], ],
}, },
deliveryTypes: { deliveryTypes: {
rules: [ rules: [
{ {
required: true, required: true,
errorMessage: '请选择商品物流', errorMessage: '请选择商品物流',
}, },
], ],
}, },
} }
const formRef = ref(null) const formRef = ref(null)
const pickerMode = ref('single')
const popMark = ref('')
const categoryList = ref([])
const optionsCols = ref([])
function openPicker() { function openPicker(mark, mode) {
console.log('123') pickerMode.value = mode
popupRef.value.open() popMark.value = mark
if (mark === 'delivery') {
optionsCols.value = DELIVERY_TYPES
pickerRef.value.onOpen([0])
} else if (mark === 'category') {
optionsCols.value = categoryList.value
pickerRef.value.onOpen([0, 0])
}
}
function onRDPickerConfirm(e) {
if (popMark.value === 'delivery') {
formData.value.deliveryTypes = []
formData.value.deliveryText = DELIVERY_TYPES[e.value[0]].label
formData.value.deliveryTypes.push(DELIVERY_TYPES[e.value[0]].value)
}
if (popMark.value === 'category') {
formData.value.categoryId = e.value
formData.value.categoryText =
categoryList.value[e.value[0]].name + '/' + categoryList.value[e.value[0]].children[e.value[1]].name
}
}
function getProduct() {
GoodsApi.getProduct(formData.value.id).then((res) => {
formData.value = res
})
} }
function onSubmit() { function onSubmit() {
console.log('res', formData.value) console.log('res', formData.value)
formRef.value formRef.value
.validate() .validate()
.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)
}) })
} }
// //
function getGoodsInfo() { } function getGoodsInfo() {}
//
async function getCategoryList() {
let { data } = await GoodsApi.getGoodsCategory()
categoryList.value = handleTree(data)
}
//
async function getBrandList() {
let { data } = await GoodsApi.getBrandList()
console.log(data)
}
onLoad((options) => { onLoad((options) => {
if (options.id) { getCategoryList()
getProduct() if (options.id) {
} getProduct()
}
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.manage-goods { .manage-goods {
.goods-form { .goods-form {
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;
}
} }
uni-icons {
margin-right: 10px;
}
.uni-easyinput__content {
border-radius: 0 10px 10px 0;
}
}
.is-disabled {
color: #333333;
text-align: right;
}
} }
}
.picker-view {
width: 750rpx;
height: 600rpx;
margin-top: 20rpx;
}
.item {
line-height: 100rpx;
text-align: center;
}
} }
</style> </style>

111
pages/product/sku.vue Normal file
View File

@ -0,0 +1,111 @@
<template>
<pb-layout
class="goods-property"
title="商品属性"
leftIcon="leftIcon"
navbar="normal"
:bgStyle="bgStyle"
opacityBgUi="bg-white"
color="black"
>
<view class="property">
<uni-forms ref="formRef" v-model="formData" :rules="rules" label-position="top" label-width="160">
<uni-forms-item
label="商品规格"
@tap="pickerRef.onOpen([0])"
name="specType"
label-position="left"
required
>
<uni-easyinput
type="text"
:clearable="false"
:styles="selfStyles"
placeholderStyle="color:#8a8a8a"
:inputBorder="false"
v-model="formData.specText"
placeholder="请选择商品规格"
disabled
>
<template v-slot:right>
<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="propertyListRef.onOpen()">+添加规格</button>
</template>
<template v-else>
<SkuItem />
</template>
<p-picker ref="pickerRef" mode="single" :options-cols="SPEC_TYPE" @confirm="onRDPickerConfirm"></p-picker>
<PropertyList ref="propertyListRef" v-model="propertyList" @confirm="onPropertyConfirm" />
</pb-layout>
</template>
<script setup>
import SkuItem from './components/item'
import PropertyList from './components/propertyList'
import { SPEC_TYPE } from './js/config'
import { pickerRef, propertyListRef, onRDPickerConfirm, formData, propertyList, onPropertyConfirm } from './js/sku'
const bgStyle = {
backgroundImage: '',
backgroundColor: '#fff',
description: '',
}
</script>
<style lang="scss" scoped>
.goods-property {
.property {
margin: 40rpx;
:deep() {
.uni-easyinput__content-input {
font-size: 28rpx !important;
color: #333333 !important;
line-height: normal !important;
}
.uni-easyinput__placeholder-class {
font-size: 14px;
}
.is-direction-left {
.uni-forms-item__label {
padding-left: 10px;
background-color: #f9f9f9;
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 {
margin: 40rpx;
border: 1px dotted var(--ui-BG-Main);
color: var(--ui-BG-Main);
border-radius: 10px;
text-align: center;
}
}
</style>

View File

@ -33,6 +33,15 @@ const GoodsApi = {
data, data,
}) })
}, },
// 商品分类
getGoodsCategory: (data) => {
return request({
url: '/product/category/list',
method: 'GET',
params: data,
})
},
// 商品品牌
} }
export default GoodsApi export default GoodsApi

View File

@ -0,0 +1,167 @@
<template>
<view class="custom-picker">
<!-- 普通弹窗 -->
<uni-popup type="bottom" ref="pickerPopupRef" background-color="#fff">
<view class="popup-header">
<view class="button-cancel" @click="onClosePopup">取消</view>
<view class="button-link" @click="onConfirmPopup">确定</view>
</view>
<view class="popup-content">
<picker-view
:indicator-style="indicatorStyle"
:value="pickerViewValue"
@change="bindChange"
class="picker-view"
>
<template v-if="mode === 'single'">
<picker-view-column>
<view
class="item"
v-for="(item, index) in props.optionsCols"
:key="`${item.value}-${index}`"
>{{ item.label }}</view
>
</picker-view-column>
</template>
<template v-else>
<picker-view-column>
<view
class="item"
v-for="(item, index) in props.optionsCols"
:key="`${item.value}-${index}`"
>{{ item.name }}</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>
<script setup>
import { defineEmits, defineProps, ref, onMounted, computed, defineExpose } from 'vue'
const pickerViewValue = ref([])
const indicatorStyle = `height: 50px`
const props = defineProps({
// pickerview value label
optionsCols: {
default: () => [],
required: true,
type: Array,
},
mode: {
type: String,
default: 'single',
},
})
const childrenList = computed(() => {
return props.optionsCols[pickerViewValue.value[0]]?.children
})
// confirm change
const emit = defineEmits(['confirm', 'change'])
// ref
const pickerPopupRef = ref(null)
/**
* @author: joey wong
* @description: 打开弹窗
* @param {Array} defaultValue 默认选中index的值,[0,1]
* @return {*}
*/
const onOpen = (defaultValue) => {
pickerViewValue.value = defaultValue
pickerPopupRef.value.open('bottom')
}
/**
* @author: joey wong
* @description: 滚动改变事件
* @param {*} e
* @return {*}
*/
const bindChange = (e) => {
pickerViewValue.value = e.detail.value
if (props.mode === 'multiple') {
if (pickerViewValue.value[0] !== e.detail.value[0]) {
pickerViewValue.value[1] = 0
}
}
emit('change', {
value: pickerViewValue.value,
})
}
/**
* @author: joey wong
* @description: 关闭模态框事件
* @return {*}
*/
const onClosePopup = () => {
pickerPopupRef.value.close()
}
/**
* @author: joey wong
* @description: 确认事件
* @return {*}
*/
const onConfirmPopup = () => {
emit('confirm', {
value: pickerViewValue.value,
})
onClosePopup()
}
defineExpose({
onOpen,
})
</script>
<style lang="scss" scoped>
.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: 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>

View File

@ -1,5 +1,61 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
/**
* 构造树型结构数据
* @param {*} data 数据源
* @param {*} id id字段 默认 'id'
* @param {*} parentId 父节点字段 默认 'parentId'
* @param {*} children 孩子节点字段 默认 'children'
*/
export const handleTree = (data, id, parentId, children) => {
if (!Array.isArray(data)) {
console.warn('data must be an array')
return []
}
const config = {
id: id || 'id',
parentId: parentId || 'parentId',
childrenList: children || 'children',
}
const childrenListMap = {}
const nodeIds = {}
const tree = []
for (const d of data) {
const parentId = d[config.parentId]
if (childrenListMap[parentId] == null) {
childrenListMap[parentId] = []
}
nodeIds[d[config.id]] = d
childrenListMap[parentId].push(d)
}
for (const d of data) {
const parentId = d[config.parentId]
if (nodeIds[parentId] == null) {
tree.push(d)
}
}
for (const t of tree) {
adaptToChildrenList(t)
}
function adaptToChildrenList(o) {
if (childrenListMap[o[config.id]] !== null) {
o[config.childrenList] = childrenListMap[o[config.id]]
}
if (o[config.childrenList]) {
for (const c of o[config.childrenList]) {
adaptToChildrenList(c)
}
}
}
return tree
}
export function resetPagination(pagination) { export function resetPagination(pagination) {
pagination.list = [] pagination.list = []
pagination.total = 0 pagination.total = 0