Commit 1679242b by qinjianhui

feat: 商品类型组件封装

parent dc034270
...@@ -14,7 +14,7 @@ export interface SearchForm { ...@@ -14,7 +14,7 @@ export interface SearchForm {
orderNumber?: string orderNumber?: string
customerOrderNumber?: string customerOrderNumber?: string
shopOrderNumber?: string shopOrderNumber?: string
productType?: string | number productType?: string | number | (string | number)[]
multi?: boolean | null multi?: boolean | null
timeType?: number | null timeType?: number | null
startTime?: string | null startTime?: string | null
......
<template>
<ElPopover
placement="bottom-start"
:width="420"
trigger="click"
:teleported="false"
popper-class="product-type-filter-popper"
>
<div class="ptf-panel">
<div v-for="group in options" :key="String(group.value)" class="ptf-group">
<div class="ptf-group-header">
<div class="ptf-group-title">{{ group.label }}</div>
<div class="ptf-group-actions">
<ElCheckbox
v-if="isMulti"
:model-value="isGroupAllChecked(group)"
:indeterminate="isGroupIndeterminate(group)"
@change="toggleGroupAll(group)"
/>
<template v-if="isMulti">
<span class="ptf-select-all" @click="toggleGroupAll(group)">全选</span>
</template>
<template v-else>
<ElCheckbox
:model-value="isGroupAllChecked(group)"
:indeterminate="isGroupIndeterminate(group)"
@change="toggleGroupAll(group)"
/>
<span class="ptf-select-all" @click="toggleGroupAll(group)">全选</span>
</template>
</div>
</div>
<div class="ptf-items">
<div
v-for="item in group.children"
:key="String(item.value)"
class="ptf-item"
:class="{ active: isSelected(item.value) }"
@click="handleItemClick(item.value)"
>
<ElCheckbox
:model-value="isSelected(item.value)"
@change="() => handleItemClick(item.value)"
@click.stop
/>
<span class="ptf-item-label">{{ item.label }}</span>
</div>
</div>
</div>
</div>
<template #reference>
<ElInput
class="ptf-trigger"
:model-value="displayText"
:placeholder="placeholder"
readonly
clearable
@clear="clearValue"
>
<template #suffix>
<el-icon class="ptf-arrow"><ArrowDown /></el-icon>
</template>
</ElInput>
</template>
</ElPopover>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { ArrowDown } from '@element-plus/icons-vue'
import type { ProductTypeGroup, ProductTypeValue } from './productTypeFilterTypes'
type Model = ProductTypeValue | ProductTypeValue[] | null | undefined
const props = withDefaults(
defineProps<{
modelValue: Model
options: ProductTypeGroup[]
multiple?: boolean
placeholder?: string
}>(),
{
multiple: false,
placeholder: '请选择',
},
)
const emit = defineEmits<{
(e: 'update:modelValue', v: Model): void
}>()
const isMulti = computed(() => props.multiple || Array.isArray(props.modelValue))
const valueArray = computed<ProductTypeValue[]>(() => {
if (!isMulti.value) return []
const v = props.modelValue
return Array.isArray(v) ? v : v == null ? [] : [v]
})
const labelMap = computed(() => {
const map = new Map<ProductTypeValue, string>()
for (const g of props.options) {
map.set(g.value, g.label)
for (const c of g.children) {
map.set(c.value, c.label)
}
}
return map
})
const displayText = computed(() => {
const v = props.modelValue
if (isMulti.value) {
const arr = valueArray.value
if (!arr.length) return ''
return arr.map((x) => labelMap.value.get(x) ?? String(x)).join(',')
}
if (v == null || Array.isArray(v)) return ''
return labelMap.value.get(v) ?? String(v)
})
const isSelected = (v: ProductTypeValue) => {
if (isMulti.value) return valueArray.value.includes(v)
const cur = props.modelValue
return !Array.isArray(cur) && cur != null && cur === v
}
const selectSingle = (v: ProductTypeValue) => {
emit('update:modelValue', v)
}
const handleItemClick = (v: ProductTypeValue) => {
if (!isMulti.value) {
selectSingle(v)
return
}
const next = new Set(valueArray.value)
if (next.has(v)) next.delete(v)
else next.add(v)
emit('update:modelValue', Array.from(next))
}
const isGroupAllChecked = (group: ProductTypeGroup) => {
if (!isMulti.value) return false
const set = new Set(valueArray.value)
return group.children.length > 0 && group.children.every((c) => set.has(c.value))
}
const isGroupIndeterminate = (group: ProductTypeGroup) => {
if (!isMulti.value) return false
const set = new Set(valueArray.value)
const checkedCount = group.children.filter((c) => set.has(c.value)).length
return checkedCount > 0 && checkedCount < group.children.length
}
const toggleGroupAll = (group: ProductTypeGroup) => {
if (!group.children?.length) return
const nextAll = group.children.map((c) => c.value)
if (!isMulti.value) {
emit('update:modelValue', nextAll)
return
}
const set = new Set(valueArray.value)
const allChecked = isGroupAllChecked(group)
for (const c of group.children) {
if (allChecked) set.delete(c.value)
else set.add(c.value)
}
emit('update:modelValue', Array.from(set))
}
const clearValue = () => {
emit('update:modelValue', isMulti.value ? [] : null)
}
</script>
<style scoped lang="scss">
.ptf-panel {
padding: 6px 0;
max-height: 360px;
overflow: auto;
:deep(.el-checkbox) {
margin-right: 2px !important;
}
}
.ptf-group {
border: 1px solid #e5e6eb;
border-top: 0;
}
.ptf-group:first-child {
border-top: 1px solid #e5e6eb;
}
.ptf-group-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
background: #fff;
}
.ptf-group-title {
font-size: 14px;
font-weight: 600;
color: #1f2329;
}
.ptf-group-actions {
display: inline-flex;
align-items: center;
gap: 10px;
color: #1f2329;
user-select: none;
}
.ptf-select-all {
cursor: pointer;
font-size: 13px;
}
.ptf-items {
background: #f5f6f7;
padding: 6px 0;
}
.ptf-item {
display: inline-flex;
align-items: center;
gap: 16px;
padding: 0px 12px;
box-sizing: border-box;
cursor: pointer;
}
.ptf-item-label {
font-size: 14px;
color: #1f2329;
}
.ptf-item.active .ptf-item-label {
color: #409eff;
}
.ptf-trigger :deep(.el-input__wrapper) {
cursor: pointer;
}
.ptf-arrow {
color: #909399;
}
</style>
<style lang="scss">
.product-type-filter-popper {
padding: 0 !important;
}
</style>
export type ProductTypeValue = string | number
export interface ProductTypeItem {
label: string
value: ProductTypeValue
}
export interface ProductTypeGroup {
label: string
/** 单选时允许直接选择分组本身 */
value: ProductTypeValue
children: ProductTypeItem[]
}
...@@ -118,10 +118,11 @@ ...@@ -118,10 +118,11 @@
</ElFormItem> </ElFormItem>
<ElFormItem label="商品类型"> <ElFormItem label="商品类型">
<ElInput <ProductTypeFilter
v-model.trim="searchForm.customerOrderNumber" v-model="searchForm.productType"
placeholder="客户单号" :options="productTypeGroups"
clearable :multiple="false"
placeholder="请选择商品类型"
style="width: 140px" style="width: 140px"
/> />
</ElFormItem> </ElFormItem>
...@@ -547,6 +548,10 @@ import { ...@@ -547,6 +548,10 @@ import {
import { getUserMarkList } from '@/api/common' import { getUserMarkList } from '@/api/common'
import { getAllCountryApi } from '@/api/logistics' import { getAllCountryApi } from '@/api/logistics'
import LogisticsWaySelect from '@/views/logistics/components/LogisticsWaySelect' import LogisticsWaySelect from '@/views/logistics/components/LogisticsWaySelect'
import { IAllList } from '@/types/api/podUsOrder'
import { CraftListData } from '@/types/api/podCnOrder'
import ProductTypeFilter from './component/ProductTypeFilter.vue'
import type { ProductTypeGroup } from './component/productTypeFilterTypes'
const defaultStatusTree: StatusTreeNode[] = [ const defaultStatusTree: StatusTreeNode[] = [
{ {
code: 'ALL', code: 'ALL',
...@@ -610,6 +615,35 @@ const sourceList = [ ...@@ -610,6 +615,35 @@ const sourceList = [
const sizes = ['FS', 'XS', 'S', 'M', 'L', 'XL', 'XXL', '3XL', '4XL', '5XL'] const sizes = ['FS', 'XS', 'S', 'M', 'L', 'XL', 'XXL', '3XL', '4XL', '5XL']
const productTypeGroups = ref<ProductTypeGroup[]>([
{
label: '普品',
value: 'NORMAL_ALL',
children: [{ label: '普品', value: 'NORMAL' }],
},
{
label: '胚衣',
value: 'GREIGE_ALL',
children: [{ label: '胚衣', value: 'GREIGE' }],
},
{
label: 'POD商品',
value: 'POD_ALL',
children: [
{ label: '单面', value: 'POD_SINGLE' },
{ label: '多面', value: 'POD_MULTI' },
],
},
{
label: '一件定制局部印',
value: 'CUSTOM_PART_ALL',
children: [
{ label: '单面', value: 'CUSTOM_PART_SINGLE' },
{ label: '多面', value: 'CUSTOM_PART_MULTI' },
],
},
])
const changeReplaceShipment = () => { const changeReplaceShipment = () => {
searchForm.value.shipmentType = '' searchForm.value.shipmentType = ''
} }
...@@ -652,35 +686,53 @@ const getLogisticsCompanyAllCodelist = async () => { ...@@ -652,35 +686,53 @@ const getLogisticsCompanyAllCodelist = async () => {
/* empty */ /* empty */
} }
} }
interface ProcessTypeData {
type CraftOption = { id: string; name: string } label: string
const craftList = ref<CraftOption[]>([]) value: string
type CraftApiItem = {
craftCode?: string | number
craftName?: string
id?: string | number
name?: string
} }
const processType = ref<ProcessTypeData[]>([
{
label: '烫画',
value: 'TH',
},
{
label: '直喷',
value: 'ZP',
},
{
label: '刺绣',
value: 'CX',
},
{
label: '雕刻',
value: 'DK',
},
{
label: '白胚',
value: 'BP',
},
{
label: '其他',
value: 'QT',
},
])
const processTypeMap = processType.value.reduce((acc, cur) => {
acc[cur.value] = cur.label
return acc
}, {} as Record<string, string>)
const craftList = ref<IAllList[]>([])
const loadCraftList = async () => { const loadCraftList = async () => {
try { try {
const res = await getListCraftApi() const res = await getListCraftApi()
// 后端返回结构在不同模块里不完全一致,这里做一次宽松兼容 if (res.code !== 200) return
const root = res as unknown as { data?: unknown } const data: CraftListData[] = res.data
const data = craftList.value = data.map((item) => ({
(root.data as { data?: unknown; list?: unknown } | undefined)?.data ?? id: item.craftCode,
(root.data as { list?: unknown } | undefined)?.list ?? name: item.craftName,
root.data warehouseName: processTypeMap[item.craftType] ?? '其他',
const list = Array.isArray(data) ? (data as CraftApiItem[]) : []
craftList.value = list
.map((item) => ({
id: String(item.craftCode ?? item.id ?? ''),
name: String(item.craftName ?? item.name ?? ''),
})) }))
.filter((i) => i.id && i.name) } catch (_e) {
} catch (e) { /* empty */
console.error(e)
craftList.value = []
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment