Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
F
factory_front
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
qinjianhui
factory_front
Commits
1679242b
Commit
1679242b
authored
Mar 18, 2026
by
qinjianhui
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 商品类型组件封装
parent
dc034270
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
358 additions
and
29 deletions
+358
-29
src/types/api/factoryOrderNew.ts
+1
-1
src/views/order/factoryOrderNew/component/ProductTypeFilter.vue
+263
-0
src/views/order/factoryOrderNew/component/productTypeFilterTypes.ts
+14
-0
src/views/order/factoryOrderNew/index.vue
+80
-28
No files found.
src/types/api/factoryOrderNew.ts
View file @
1679242b
...
...
@@ -14,7 +14,7 @@ export interface SearchForm {
orderNumber
?:
string
customerOrderNumber
?:
string
shopOrderNumber
?:
string
productType
?:
string
|
number
productType
?:
string
|
number
|
(
string
|
number
)[]
multi
?:
boolean
|
null
timeType
?:
number
|
null
startTime
?:
string
|
null
...
...
src/views/order/factoryOrderNew/component/ProductTypeFilter.vue
0 → 100644
View file @
1679242b
<
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
>
src/views/order/factoryOrderNew/component/productTypeFilterTypes.ts
0 → 100644
View file @
1679242b
export
type
ProductTypeValue
=
string
|
number
export
interface
ProductTypeItem
{
label
:
string
value
:
ProductTypeValue
}
export
interface
ProductTypeGroup
{
label
:
string
/** 单选时允许直接选择分组本身 */
value
:
ProductTypeValue
children
:
ProductTypeItem
[]
}
src/views/order/factoryOrderNew/index.vue
View file @
1679242b
...
...
@@ -118,10 +118,11 @@
<
/ElFormItem
>
<
ElFormItem
label
=
"商品类型"
>
<
ElInput
v
-
model
.
trim
=
"searchForm.customerOrderNumber"
placeholder
=
"客户单号"
clearable
<
ProductTypeFilter
v
-
model
=
"searchForm.productType"
:
options
=
"productTypeGroups"
:
multiple
=
"false"
placeholder
=
"请选择商品类型"
style
=
"width: 140px"
/>
<
/ElFormItem
>
...
...
@@ -547,6 +548,10 @@ import {
import
{
getUserMarkList
}
from
'@/api/common'
import
{
getAllCountryApi
}
from
'@/api/logistics'
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
[]
=
[
{
code
:
'ALL'
,
...
...
@@ -610,6 +615,35 @@ const sourceList = [
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
=
()
=>
{
searchForm
.
value
.
shipmentType
=
''
}
...
...
@@ -652,35 +686,53 @@ const getLogisticsCompanyAllCodelist = async () => {
/* empty */
}
}
type
CraftOption
=
{
id
:
string
;
name
:
string
}
const
craftList
=
ref
<
CraftOption
[]
>
([])
type
CraftApiItem
=
{
craftCode
?:
string
|
number
craftName
?:
string
id
?:
string
|
number
name
?:
string
interface
ProcessTypeData
{
label
:
string
value
:
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
()
=>
{
try
{
const
res
=
await
getListCraftApi
()
// 后端返回结构在不同模块里不完全一致,这里做一次宽松兼容
const
root
=
res
as
unknown
as
{
data
?:
unknown
}
const
data
=
(
root
.
data
as
{
data
?:
unknown
;
list
?:
unknown
}
|
undefined
)?.
data
??
(
root
.
data
as
{
list
?:
unknown
}
|
undefined
)?.
list
??
root
.
data
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
??
''
),
if
(
res
.
code
!==
200
)
return
const
data
:
CraftListData
[]
=
res
.
data
craftList
.
value
=
data
.
map
((
item
)
=>
({
id
:
item
.
craftCode
,
name
:
item
.
craftName
,
warehouseName
:
processTypeMap
[
item
.
craftType
]
??
'其他'
,
}
))
.
filter
((
i
)
=>
i
.
id
&&
i
.
name
)
}
catch
(
e
)
{
console
.
error
(
e
)
craftList
.
value
=
[]
}
catch
(
_e
)
{
/* empty */
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment