Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
bdf95dc0
authored
Jan 11, 2024
by
owen
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/dev' into dev
parents
a9c231be
a5d6d18b
Hide whitespace changes
Inline
Side-by-side
Showing
24 changed files
with
635 additions
and
414 deletions
+635
-414
.env.dev
+1
-0
src/api/crm/contact/index.ts
+6
-0
src/api/crm/customer/index.ts
+15
-2
src/api/crm/permission/index.ts
+0
-12
src/api/mall/promotion/reward/rewardActivity.ts
+48
-0
src/api/mall/trade/config/index.ts
+0
-1
src/components/AppLinkInput/data.ts
+0
-4
src/components/OperateLogV2/src/OperateLogV2.vue
+9
-79
src/components/UploadFile/src/UploadFile.vue
+4
-1
src/utils/constants.ts
+3
-3
src/utils/index.ts
+4
-4
src/views/Login/Login.vue
+1
-1
src/views/crm/contact/ContactForm.vue
+3
-2
src/views/crm/contact/detail/ContactDetailsInfo.vue
+67
-65
src/views/crm/contact/detail/index.vue
+19
-2
src/views/crm/customer/detail/CustomerDetailsHeader.vue
+2
-43
src/views/crm/customer/detail/index.vue
+89
-23
src/views/crm/customer/index.vue
+105
-14
src/views/crm/permission/components/PermissionList.vue
+9
-5
src/views/mall/product/spu/index.vue
+70
-64
src/views/mall/promotion/rewardActivity/RewardForm.vue
+172
-43
src/views/mall/promotion/rewardActivity/index.vue
+5
-27
src/views/mall/statistics/product/components/ProductRank.vue
+3
-6
src/views/mall/trade/config/index.vue
+0
-13
No files found.
.env.dev
View file @
bdf95dc0
...
...
@@ -5,6 +5,7 @@ VITE_DEV=true
# 请求路径
VITE_BASE_URL='http://api-dashboard.yudao.iocoder.cn'
# VITE_BASE_URL='http://dofast.demo.huizhizao.vip:20001'
# 上传路径
VITE_UPLOAD_URL='http://api-dashboard.yudao.iocoder.cn/admin-api/infra/file/upload'
...
...
src/api/crm/contact/index.ts
View file @
bdf95dc0
...
...
@@ -80,3 +80,8 @@ export const createContactBusinessList = async (data: ContactBusinessReqVO) => {
export
const
deleteContactBusinessList
=
async
(
data
:
ContactBusinessReqVO
)
=>
{
return
await
request
.
delete
({
url
:
`/crm/contact/delete-business-list`
,
data
})
}
// 查询联系人操作日志
export
const
getOperateLogPage
=
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
'/crm/contact/operate-log-page'
,
params
})
}
\ No newline at end of file
src/api/crm/customer/index.ts
View file @
bdf95dc0
...
...
@@ -69,11 +69,24 @@ export const queryAllList = async () => {
}
// 查询客户操作日志
export
const
getOperateLogPage
=
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
'/crm/customer/operate-log-page
'
,
params
})
export
const
getOperateLogPage
=
async
(
id
:
number
)
=>
{
return
await
request
.
get
({
url
:
'/crm/customer/operate-log-page
?id='
+
id
})
}
// ======================= 业务操作 =======================
// 锁定/解锁客户
export
const
lockCustomer
=
async
(
id
:
number
,
lockStatus
:
boolean
)
=>
{
return
await
request
.
put
({
url
:
`/crm/customer/lock`
,
data
:
{
id
,
lockStatus
}
})
}
// TODO @puhui999:方法名,改成和后端一致哈
// 领取公海客户
export
const
receive
=
async
(
ids
:
any
[])
=>
{
return
await
request
.
put
({
url
:
'/crm/customer/receive'
,
params
:
{
ids
:
ids
.
join
(
','
)
}
})
}
// 客户放入公海
export
const
putPool
=
async
(
id
:
number
)
=>
{
return
await
request
.
put
({
url
:
`/crm/customer/put-pool?id=
${
id
}
`
})
}
src/api/crm/permission/index.ts
View file @
bdf95dc0
...
...
@@ -58,15 +58,3 @@ export const deletePermissionBatch = async (params) => {
export
const
deleteSelfPermission
=
async
(
id
)
=>
{
return
await
request
.
delete
({
url
:
'/crm/permission/quit-team?id='
+
id
})
}
// TODO @puhui999:调整下位置
// 领取公海数据
export
const
receive
=
async
(
data
:
{
bizType
:
number
;
bizId
:
number
})
=>
{
return
await
request
.
put
({
url
:
`/crm/permission/receive`
,
data
})
}
// TODO @puhui999:调整下位置
// 数据放入公海
export
const
putPool
=
async
(
data
:
{
bizType
:
number
;
bizId
:
number
})
=>
{
return
await
request
.
put
({
url
:
`/crm/permission/put-pool`
,
data
})
}
src/api/mall/promotion/reward/rewardActivity.ts
0 → 100644
View file @
bdf95dc0
import
request
from
'@/config/axios'
export
interface
DiscountActivityVO
{
id
?:
number
name
?:
string
startTime
?:
Date
endTime
?:
Date
remark
?:
string
conditionType
?:
number
productScope
?:
number
productSpuIds
?:
number
[]
rules
?:
DiscountProductVO
[]
}
// 优惠规则
export
interface
DiscountProductVO
{
limit
:
number
discountPrice
:
number
freeDelivery
:
boolean
point
:
number
couponIds
:
number
[]
couponCounts
:
number
[]
}
// 新增满减送活动
export
const
createRewardActivity
=
async
(
data
:
DiscountActivityVO
)
=>
{
return
await
request
.
post
({
url
:
'/promotion/reward-activity/create'
,
data
})
}
// 更新满减送活动
export
const
updateRewardActivity
=
async
(
data
:
DiscountActivityVO
)
=>
{
return
await
request
.
put
({
url
:
'/promotion/reward-activity/update'
,
data
})
}
// 查询满减送活动列表
export
const
getRewardActivityPage
=
async
(
params
)
=>
{
return
await
request
.
get
({
url
:
'/promotion/reward-activity/page'
,
params
})
}
// 查询满减送活动详情
export
const
getReward
=
async
(
id
:
number
)
=>
{
return
await
request
.
get
({
url
:
'/promotion/reward-activity/get?id='
+
id
})
}
// 删除限时折扣活动
export
const
deleteRewardActivity
=
async
(
id
:
number
)
=>
{
return
await
request
.
delete
({
url
:
'/promotion/reward-activity/delete?id='
+
id
})
}
src/api/mall/trade/config/index.ts
View file @
bdf95dc0
...
...
@@ -8,7 +8,6 @@ export interface ConfigVO {
brokerageFirstPercent
:
number
brokerageSecondPercent
:
number
brokerageWithdrawMinPrice
:
number
brokerageBankNames
:
string
brokerageFrozenDays
:
number
brokerageWithdrawTypes
:
string
}
...
...
src/components/AppLinkInput/data.ts
View file @
bdf95dc0
...
...
@@ -181,10 +181,6 @@ export const APP_LINK_GROUP_LIST = [
{
name
:
'充值记录'
,
path
:
'/pages/pay/recharge-log'
},
{
name
:
'申请提现'
,
path
:
'/pages/pay/withdraw'
}
]
},
...
...
src/components/OperateLogV2/src/OperateLogV2.vue
View file @
bdf95dc0
<
template
>
<!-- TODO @puhui999:左边不用有空隙哈 -->
<div
class=
"p-20px"
>
<el-timeline>
<el-timeline-item
v-for=
"(log, index) in log
Data
List"
v-for=
"(log, index) in logList"
:key=
"index"
:timestamp=
"formatDate(log.createTime)"
placement=
"top"
>
<div
class=
"el-timeline-right-content"
>
<el-row>
<el-col
:span=
"24"
class=
"mb-10px"
>
=======================
<el-tag
class=
"mr-10px"
type=
"success"
>
{{
log
.
userName
}}
</el-tag>
<span>
{{
log
.
title
}}
</span>
=======================
</el-col>
<!-- 先处理一下有几行-->
<template
v-for=
"colNum in log.colSize"
:key=
"colNum + 'col'"
>
<el-col
:span=
"24"
class=
"mb-10px"
>
<!-- 处理每一行-->
<template
v-for=
"(tagVal, index2) in log.tagsContentList.slice(
(colNum - 1) * 3,
3 * colNum
)"
:key=
"index2"
>
<el-tag
class=
"mx-10px"
>
{{
tagVal
}}
</el-tag>
<span>
{{
log
.
contentStrList
[
index2
]
}}
</span>
</
template
>
</el-col>
</template>
</el-row>
<el-tag
class=
"mr-10px"
type=
"success"
>
{{
log
.
userName
}}
</el-tag>
{{
log
.
action
}}
</div>
<template
#
dot
>
<span
:style=
"
{ backgroundColor: getUserTypeColor(log.userType) }" class="dot-node-style">
...
...
@@ -51,11 +30,13 @@ import { ElTag } from 'element-plus'
defineOptions
({
name
:
'OperateLogV2'
})
const
props
=
defineProps
<
{
interface
Props
{
logList
:
OperateLogV2VO
[]
// 操作日志列表
}
>
()
}
const
logDataList
=
ref
<
OperateLogV2VO
[]
>
([])
// 操作日志列表
withDefaults
(
defineProps
<
Props
>
(),
{
logList
:
()
=>
[]
})
/** 获得 userType 颜色 */
const
getUserTypeColor
=
(
type
:
number
)
=>
{
...
...
@@ -72,57 +53,6 @@ const getUserTypeColor = (type: number) => {
}
return
'#409EFF'
}
// 提取 tag 所需内容和位置
const
renderTags
=
(
content
:
string
)
=>
{
let
newStr
=
unref
(
content
).
slice
()
// 去掉引用
newStr
=
newStr
.
replaceAll
(
'【】'
,
'【空】'
).
replaceAll
(
';'
,
''
)
// 处理掉分号 特殊:处理一下空的情况
const
regex
=
/【
([^
【】
]
+
)
】/g
const
fg
=
'|'
// 原始位置替换符号
let
match
:
any
[]
|
null
let
matchStr
:
string
[]
=
[]
let
oldStr
:
string
[]
=
[]
while
((
match
=
regex
.
exec
(
newStr
))
!==
null
)
{
matchStr
.
push
(
match
[
1
])
// 提取值
oldStr
.
push
(
match
[
0
])
// 原值
}
// 为什么重新循环不放在 while 中一起是因为替换重新赋值过后 match 值就不准确了
oldStr
.
forEach
((
item
)
=>
{
newStr
=
newStr
.
replace
(
item
,
fg
)
})
return
[
newStr
.
split
(
fg
),
matchStr
]
}
const
initLog
=
()
=>
{
logDataList
.
value
=
props
.
logList
.
map
((
logItem
)
=>
{
const
keyValue
=
renderTags
(
logItem
.
action
)
// 挂载数据
logItem
.
contentStrList
=
keyValue
[
0
]
if
(
keyValue
[
0
][
0
]
===
'从'
)
{
logItem
.
title
=
logItem
.
subType
}
else
{
logItem
.
title
=
keyValue
[
0
][
0
]
logItem
.
contentStrList
.
splice
(
0
,
1
)
}
logItem
.
colSize
=
keyValue
[
0
].
length
/
3
// 变更记录行数
logItem
.
tagsContentList
=
keyValue
[
1
]
return
logItem
})
}
watch
(
()
=>
props
.
logList
.
length
,
(
newObj
)
=>
{
if
(
newObj
)
{
initLog
()
console
.
log
(
logDataList
.
value
)
}
},
{
immediate
:
true
,
deep
:
true
}
)
</
script
>
<
style
lang=
"scss"
scoped
>
...
...
src/components/UploadFile/src/UploadFile.vue
View file @
bdf95dc0
...
...
@@ -100,7 +100,9 @@ const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
// 文件上传成功
const
handleFileSuccess
:
UploadProps
[
'onSuccess'
]
=
(
res
:
any
):
void
=>
{
message
.
success
(
'上传成功'
)
fileList
.
value
.
shift
()
// 删除自身
const
index
=
fileList
.
value
.
findIndex
((
item
)
=>
item
.
response
?.
data
===
res
.
data
)
fileList
.
value
.
splice
(
index
,
1
)
uploadList
.
value
.
push
({
name
:
res
.
data
,
url
:
res
.
data
})
if
(
uploadList
.
value
.
length
==
uploadNumber
.
value
)
{
fileList
.
value
.
push
(...
uploadList
.
value
)
...
...
@@ -144,6 +146,7 @@ watch(
fileList
.
value
.
push
(
...
val
.
split
(
','
).
map
((
url
)
=>
({
name
:
url
.
substring
(
url
.
lastIndexOf
(
'/'
)
+
1
),
url
}))
)
return
}
// 情况2:数组
fileList
.
value
.
push
(
...
...
src/utils/constants.ts
View file @
bdf95dc0
...
...
@@ -244,15 +244,15 @@ export const CouponTemplateTakeTypeEnum = {
*/
export
const
PromotionProductScopeEnum
=
{
ALL
:
{
scope
:
1
,
scope
:
1
0
,
name
:
'通用劵'
},
SPU
:
{
scope
:
2
,
scope
:
2
0
,
name
:
'商品劵'
},
CATEGORY
:
{
scope
:
3
,
scope
:
3
0
,
name
:
'品类劵'
}
}
...
...
src/utils/index.ts
View file @
bdf95dc0
...
...
@@ -194,10 +194,10 @@ export const copyValueToTarget = (target, source) => {
* 将一个整数转换为分数保留两位小数
* @param num
*/
export
const
formatToFraction
=
(
num
:
number
|
string
|
undefined
):
number
=>
{
if
(
typeof
num
===
'undefined'
)
return
0
export
const
formatToFraction
=
(
num
:
number
|
string
|
undefined
):
string
=>
{
if
(
typeof
num
===
'undefined'
)
return
'0.00'
const
parsedNumber
=
typeof
num
===
'string'
?
parseFloat
(
num
)
:
num
return
parseFloat
((
parsedNumber
/
100
).
toFixed
(
2
)
)
return
(
parsedNumber
/
100.0
).
toFixed
(
2
)
}
/**
...
...
@@ -249,7 +249,7 @@ export const yuanToFen = (amount: string | number): number => {
/**
* 分转元
*/
export
const
fenToYuan
=
(
price
:
string
|
number
):
number
=>
{
export
const
fenToYuan
=
(
price
:
string
|
number
):
string
=>
{
return
formatToFraction
(
price
)
}
...
...
src/views/Login/Login.vue
View file @
bdf95dc0
<
template
>
<div
:class=
"prefixCls"
class=
"relative h-[100%] lt-xl:
bg-[var(--login-bg-color)]
lt-md:px-10px lt-sm:px-10px lt-xl:px-10px"
class=
"relative h-[100%] lt-xl:
px-10px
lt-md:px-10px lt-sm:px-10px lt-xl:px-10px"
>
<div
class=
"relative mx-auto h-full flex"
>
<div
...
...
src/views/crm/contact/ContactForm.vue
View file @
bdf95dc0
...
...
@@ -136,7 +136,6 @@
</el-form-item>
</el-col>
</el-row>
<!-- TODO @zyna:解决下 ide 报错 -->
<el-row>
<el-col
:span=
"12"
>
<el-form-item
label=
"直属上级"
prop=
"parentId"
>
...
...
@@ -233,7 +232,8 @@ const ownerUserList = ref<any[]>([])
const
userList
=
ref
<
UserApi
.
UserVO
[]
>
([])
// 用户列表
// TODO 芋艿:统一的客户选择面板
const
customerList
=
ref
<
CustomerApi
.
CustomerVO
[]
>
([])
// 客户列表
const
allContactList
=
ref
([])
// 所有联系人列表
const
allContactList
=
ref
<
ContactApi
.
ContactVO
[]
>
([])
// 所有联系人列表
/** 打开弹窗 */
const
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
dialogVisible
.
value
=
true
...
...
@@ -255,6 +255,7 @@ const open = async (type: string, id?: number) => {
}
}
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
/** 提交表单 */
const
emit
=
defineEmits
([
'success'
])
// 定义 success 事件,用于操作成功后的回调
const
submitForm
=
async
()
=>
{
...
...
src/views/crm/contact/detail/ContactDetailsInfo.vue
View file @
bdf95dc0
<
template
>
<!-- TODO @zyna:少了一个外边框 -->
<el-collapse
v-model=
"activeNames"
>
<el-collapse-item
name=
"basicInfo"
>
<template
#
title
>
<span
class=
"text-base font-bold"
>
基本信息
</span>
</
template
>
<el-descriptions
:column=
"4"
>
<el-descriptions-item
label=
"姓名"
>
{{ contact.name }}
</el-descriptions-item>
<el-descriptions-item
label=
"客户"
>
{{ contact.customerName }}
</el-descriptions-item>
<el-descriptions-item
label=
"手机"
>
{{ contact.mobile }}
</el-descriptions-item>
<el-descriptions-item
label=
"座机"
>
{{ contact.telephone }}
</el-descriptions-item>
<el-descriptions-item
label=
"邮箱"
>
{{ contact.email }}
</el-descriptions-item>
<el-descriptions-item
label=
"QQ"
>
{{ contact.qq }}
</el-descriptions-item>
<el-descriptions-item
label=
"微信"
>
{{ contact.wechat }}
</el-descriptions-item>
<el-descriptions-item
label=
"下次联系时间"
>
{{ contact.nextTime ? formatDate(contact.nextTime) : '空' }}
</el-descriptions-item>
<el-descriptions-item
label=
"所在地"
>
{{ contact.areaName }}
</el-descriptions-item>
<el-descriptions-item
label=
"详细地址"
>
{{ contact.address }}
</el-descriptions-item>
<el-descriptions-item
label=
"性别"
>
<dict-tag
:type=
"DICT_TYPE.SYSTEM_USER_SEX"
:value=
"contact.sex"
/>
</el-descriptions-item>
<el-descriptions-item
label=
"备注"
>
{{ contact.remark }}
</el-descriptions-item>
</el-descriptions>
</el-collapse-item>
<el-collapse-item
name=
"systemInfo"
>
<
template
#
title
>
<span
class=
"text-base font-bold"
>
系统信息
</span>
</
template
>
<el-descriptions
:column=
"2"
>
<el-descriptions-item
label=
"负责人"
>
{{ contact.ownerUserName }}
</el-descriptions-item>
<el-descriptions-item
label=
"创建人"
>
{{ contact.creatorName }}
</el-descriptions-item>
<el-descriptions-item
label=
"创建时间"
>
{{ contact.createTime ? formatDate(contact.createTime) : '空' }}
</el-descriptions-item>
<el-descriptions-item
label=
"更新时间"
>
{{ contact.updateTime ? formatDate(contact.updateTime) : '空' }}
</el-descriptions-item>
</el-descriptions>
</el-collapse-item>
</el-collapse>
<ContentWrap>
<el-collapse
v-model=
"activeNames"
>
<el-collapse-item
name=
"basicInfo"
>
<template
#
title
>
<span
class=
"text-base font-bold"
>
基本信息
</span>
</
template
>
<el-descriptions
:column=
"4"
>
<el-descriptions-item
label=
"姓名"
>
{{ contact.name }}
</el-descriptions-item>
<el-descriptions-item
label=
"客户"
>
{{ contact.customerName }}
</el-descriptions-item>
<el-descriptions-item
label=
"手机"
>
{{ contact.mobile }}
</el-descriptions-item>
<el-descriptions-item
label=
"座机"
>
{{ contact.telephone }}
</el-descriptions-item>
<el-descriptions-item
label=
"邮箱"
>
{{ contact.email }}
</el-descriptions-item>
<el-descriptions-item
label=
"QQ"
>
{{ contact.qq }}
</el-descriptions-item>
<el-descriptions-item
label=
"微信"
>
{{ contact.wechat }}
</el-descriptions-item>
<el-descriptions-item
label=
"下次联系时间"
>
{{ contact.nextTime ? formatDate(contact.nextTime) : '空' }}
</el-descriptions-item>
<el-descriptions-item
label=
"所在地"
>
{{ contact.areaName }}
</el-descriptions-item>
<el-descriptions-item
label=
"详细地址"
>
{{ contact.detailAddress }}
</el-descriptions-item>
<el-descriptions-item
label=
"性别"
>
<dict-tag
:type=
"DICT_TYPE.SYSTEM_USER_SEX"
:value=
"contact.sex"
/>
</el-descriptions-item>
<el-descriptions-item
label=
"备注"
>
{{ contact.remark }}
</el-descriptions-item>
</el-descriptions>
</el-collapse-item>
<el-collapse-item
name=
"systemInfo"
>
<
template
#
title
>
<span
class=
"text-base font-bold"
>
系统信息
</span>
</
template
>
<el-descriptions
:column=
"2"
>
<el-descriptions-item
label=
"负责人"
>
{{ contact.ownerUserName }}
</el-descriptions-item>
<el-descriptions-item
label=
"创建人"
>
{{ contact.creatorName }}
</el-descriptions-item>
<el-descriptions-item
label=
"创建时间"
>
{{ contact.createTime ? formatDate(contact.createTime) : '空' }}
</el-descriptions-item>
<el-descriptions-item
label=
"更新时间"
>
{{ contact.updateTime ? formatDate(contact.updateTime) : '空' }}
</el-descriptions-item>
</el-descriptions>
</el-collapse-item>
</el-collapse>
</ContentWrap>
</template>
<
script
setup
lang=
"ts"
>
import
*
as
ContactApi
from
'@/api/crm/contact'
import
{
DICT_TYPE
}
from
'@/utils/dict'
import
{
formatDate
}
from
'@/utils/formatTime'
const
{
contact
}
=
defineProps
<
{
contact
:
ContactApi
.
ContactVO
}
>
()
...
...
src/views/crm/contact/detail/index.vue
View file @
bdf95dc0
...
...
@@ -5,7 +5,9 @@
<el-tab-pane
label=
"详细资料"
>
<ContactDetailsInfo
:contact=
"contact"
/>
</el-tab-pane>
<el-tab-pane
label=
"操作日志"
lazy
>
TODO 待开发
</el-tab-pane>
<el-tab-pane
label=
"操作日志"
>
<OperateLogV2
:log-list=
"logList"
/>
</el-tab-pane>
<el-tab-pane
label=
"团队成员"
lazy
>
<PermissionList
:biz-id=
"contact.id!"
:biz-type=
"BizTypeEnum.CRM_CONTACT"
/>
</el-tab-pane>
...
...
@@ -20,7 +22,6 @@
</el-col>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
ElMessage
}
from
'element-plus'
// TODO @zyna:使用 hook 引入 message
import
{
useTagsViewStore
}
from
'@/store/modules/tagsView'
import
*
as
ContactApi
from
'@/api/crm/contact'
import
ContactDetailsHeader
from
'@/views/crm/contact/detail/ContactDetailsHeader.vue'
...
...
@@ -28,6 +29,7 @@ import ContactDetailsInfo from '@/views/crm/contact/detail/ContactDetailsInfo.vu
import
BusinessList
from
'@/views/crm/business/components/BusinessList.vue'
// 商机列表
import
PermissionList
from
'@/views/crm/permission/components/PermissionList.vue'
// 团队成员列表(权限)
import
{
BizTypeEnum
}
from
'@/api/crm/permission'
import
{
OperateLogV2VO
}
from
'@/api/system/operatelog'
defineOptions
({
name
:
'CrmContactDetail'
})
...
...
@@ -41,11 +43,26 @@ const getContactData = async (id: number) => {
loading
.
value
=
true
try
{
contact
.
value
=
await
ContactApi
.
getContact
(
id
)
await
getOperateLog
(
id
)
}
finally
{
loading
.
value
=
false
}
}
/**
* 获取操作日志
*/
const
logList
=
ref
<
OperateLogV2VO
[]
>
([])
// 操作日志列表
const
getOperateLog
=
async
(
contactId
:
number
)
=>
{
if
(
!
contactId
)
{
return
}
const
data
=
await
ContactApi
.
getOperateLogPage
({
bizId
:
contactId
})
logList
.
value
=
data
.
list
}
/** 初始化 */
const
{
delView
}
=
useTagsViewStore
()
// 视图操作
const
{
currentRoute
}
=
useRouter
()
// 路由
...
...
src/views/crm/customer/detail/CustomerDetailsHeader.vue
View file @
bdf95dc0
...
...
@@ -11,19 +11,7 @@
</div>
<div>
<!-- 右上:按钮 -->
<el-button
type=
"primary"
v-hasPermi=
"['crm:customer:update']"
@
click=
"openForm(customer.id)"
>
编辑
</el-button>
<!-- TODO @puhui999:转移的操作接入 -->
<el-button
type=
"primary"
@
click=
"transfer"
>
转移
</el-button>
<!-- TODO @puhui999:修改成交状态的接入 -->
<el-button>
更改成交状态
</el-button>
<el-button
v-if=
"customer.lockStatus"
@
click=
"handleUnlock(customer.id!)"
>
解锁
</el-button>
<el-button
v-else
@
click=
"handleLock(customer.id!)"
>
锁定
</el-button>
<slot></slot>
</div>
</div>
</div>
...
...
@@ -42,43 +30,14 @@
<el-descriptions-item
label=
"首要联系人电话"
>
{{
customer
.
mobile
}}
</el-descriptions-item>
</el-descriptions>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<CustomerForm
ref=
"formRef"
@
success=
"emit('refresh')"
/>
</
template
>
<
script
lang=
"ts"
setup
>
import
{
DICT_TYPE
}
from
'@/utils/dict'
import
*
as
CustomerApi
from
'@/api/crm/customer'
import
CustomerForm
from
'../CustomerForm.vue'
defineOptions
({
name
:
'CustomerDetailsHeader'
})
const
{
customer
,
loading
}
=
defineProps
<
{
defineProps
<
{
customer
:
CustomerApi
.
CustomerVO
// 客户信息
loading
:
boolean
// 加载中
}
>
()
const
message
=
useMessage
()
// 消息弹窗
/** 修改操作 */
const
formRef
=
ref
()
const
openForm
=
(
id
?:
number
)
=>
{
formRef
.
value
.
open
(
'update'
,
id
)
}
/** 锁定操作 */
const
handleLock
=
async
(
id
:
number
)
=>
{
await
CustomerApi
.
lockCustomer
(
id
,
true
)
message
.
success
(
'锁定成功'
)
emit
(
'refresh'
)
}
/** 解锁操作 */
const
handleUnlock
=
async
(
id
:
number
)
=>
{
console
.
log
(
customer
,
'======='
)
await
CustomerApi
.
lockCustomer
(
id
,
false
)
message
.
success
(
'解锁成功'
)
emit
(
'refresh'
)
}
const
emit
=
defineEmits
([
'refresh'
])
// 定义 success 事件,用于操作成功后的回调
</
script
>
src/views/crm/customer/detail/index.vue
View file @
bdf95dc0
<
template
>
<CustomerDetailsHeader
:customer=
"customer"
:loading=
"loading"
@
refresh=
"getCustomer(id)"
/>
<CustomerDetailsHeader
:customer=
"customer"
:loading=
"loading"
>
<!-- @puhui999:返回是不是可以去掉哈,貌似用途可能不大 -->
<el-button
@
click=
"close"
>
返回
</el-button>
<!-- TODO puhui999: 按钮数据权限收尾统一完善,需要按权限分级和客户状态来动态显示匹配的按钮 -->
<el-button
v-hasPermi=
"['crm:customer:update']"
type=
"primary"
@
click=
"openForm"
>
编辑
</el-button>
<!-- TODO @puhui999:转移的操作接入 -->
<el-button
type=
"primary"
@
click=
"transfer"
>
转移
</el-button>
<!-- TODO @puhui999:修改成交状态的接入 -->
<el-button>
更改成交状态
</el-button>
<el-button
v-if=
"customer.lockStatus"
@
click=
"handleUnlock"
>
解锁
</el-button>
<el-button
v-if=
"!customer.lockStatus"
@
click=
"handleLock"
>
锁定
</el-button>
<el-button
v-if=
"!customer.ownerUserId"
type=
"primary"
@
click=
"receive"
>
领取客户
</el-button>
<el-button
v-if=
"customer.ownerUserId"
@
click=
"putPool"
>
客户放入公海
</el-button>
</CustomerDetailsHeader>
<el-col>
<el-tabs>
<el-tab-pane
label=
"详细资料"
>
...
...
@@ -11,7 +26,7 @@
<el-tab-pane
label=
"联系人"
lazy
>
<ContactList
:biz-id=
"customer.id!"
:biz-type=
"BizTypeEnum.CRM_CUSTOMER"
/>
</el-tab-pane>
<el-tab-pane
label=
"团队成员"
lazy
>
<el-tab-pane
label=
"团队成员"
>
<PermissionList
:biz-id=
"customer.id!"
:biz-type=
"BizTypeEnum.CRM_CUSTOMER"
/>
</el-tab-pane>
<el-tab-pane
label=
"商机"
lazy
>
...
...
@@ -27,10 +42,14 @@
<el-tab-pane
label=
"回访"
lazy
>
TODO 待开发
</el-tab-pane>
</el-tabs>
</el-col>
<!-- 表单弹窗:添加/修改 -->
<CustomerForm
ref=
"formRef"
@
success=
"getCustomer"
/>
</
template
>
<
script
lang=
"ts"
setup
>
import
{
useTagsViewStore
}
from
'@/store/modules/tagsView'
import
*
as
CustomerApi
from
'@/api/crm/customer'
import
CustomerForm
from
'@/views/crm/customer/CustomerForm.vue'
import
CustomerDetailsInfo
from
'./CustomerDetailsInfo.vue'
// 客户明细 - 详细信息
import
CustomerDetailsHeader
from
'./CustomerDetailsHeader.vue'
// 客户明细 - 头部
import
ContactList
from
'@/views/crm/contact/components/ContactList.vue'
// 联系人列表
...
...
@@ -40,48 +59,95 @@ import ReceivableList from '@/views/crm/receivable/components/ReceivableList.vue
import
ReceivablePlanList
from
'@/views/crm/receivable/plan/components/ReceivablePlanList.vue'
// 回款计划列表
import
PermissionList
from
'@/views/crm/permission/components/PermissionList.vue'
// 团队成员列表(权限)
import
{
BizTypeEnum
}
from
'@/api/crm/permission'
import
{
OperateLogV2VO
}
from
'@/api/system/operatelog'
import
type
{
OperateLogV2VO
}
from
'@/api/system/operatelog'
defineOptions
({
name
:
'CrmCustomerDetail'
})
const
route
=
useRoute
()
const
id
=
Number
(
route
.
params
.
id
)
// 客户编号
const
customerId
=
ref
(
0
)
// 客户编号
const
loading
=
ref
(
true
)
// 加载中
const
message
=
useMessage
()
// 消息弹窗
const
{
delView
}
=
useTagsViewStore
()
// 视图操作
const
{
currentRoute
,
push
}
=
useRouter
()
// 路由
/** 获取详情 */
const
customer
=
ref
<
CustomerApi
.
CustomerVO
>
({}
as
CustomerApi
.
CustomerVO
)
// 客户详情
const
getCustomer
=
async
(
id
:
number
)
=>
{
const
getCustomer
=
async
()
=>
{
loading
.
value
=
true
try
{
customer
.
value
=
await
CustomerApi
.
getCustomer
(
id
)
await
getOperateLog
(
id
)
customer
.
value
=
await
CustomerApi
.
getCustomer
(
customerId
.
value
)
await
getOperateLog
()
}
finally
{
loading
.
value
=
false
}
}
/** 编辑客户 */
const
formRef
=
ref
<
InstanceType
<
typeof
CustomerForm
>>
()
// 客户表单 Ref
const
openForm
=
()
=>
{
formRef
.
value
?.
open
(
'update'
,
customerId
.
value
)
}
/** 客户转移 */
const
transfer
=
()
=>
{}
/** 锁定客户 */
const
handleLock
=
async
()
=>
{
await
message
.
confirm
(
`确定锁定客户【
${
customer
.
value
.
name
}
】 吗?`
)
await
CustomerApi
.
lockCustomer
(
unref
(
customerId
.
value
),
true
)
message
.
success
(
`锁定客户【
${
customer
.
value
.
name
}
】成功`
)
await
getCustomer
()
}
/** 解锁客户 */
const
handleUnlock
=
async
()
=>
{
await
message
.
confirm
(
`确定解锁客户【
${
customer
.
value
.
name
}
】 吗?`
)
await
CustomerApi
.
lockCustomer
(
unref
(
customerId
.
value
),
false
)
message
.
success
(
`解锁客户【
${
customer
.
value
.
name
}
】成功`
)
await
getCustomer
()
}
// TODO @puhui999:下面两个方法的命名,也用 handleXXX 风格哈
/** 领取客户 */
const
receive
=
async
()
=>
{
await
message
.
confirm
(
`确定领取客户【
${
customer
.
value
.
name
}
】 吗?`
)
await
CustomerApi
.
receive
([
unref
(
customerId
.
value
)])
message
.
success
(
`领取客户【
${
customer
.
value
.
name
}
】成功`
)
await
getCustomer
()
}
/** 客户放入公海 */
const
putPool
=
async
()
=>
{
await
message
.
confirm
(
`确定将客户【
${
customer
.
value
.
name
}
】放入公海吗?`
)
await
CustomerApi
.
putPool
(
unref
(
customerId
.
value
))
message
.
success
(
`客户【
${
customer
.
value
.
name
}
】放入公海成功`
)
close
()
}
/** 获取操作日志 */
const
logList
=
ref
<
OperateLogV2VO
[]
>
([])
// 操作日志列表
/**
* 获取操作日志
*/
const
getOperateLog
=
async
(
customerId
:
number
)
=>
{
if
(
!
customerId
)
{
const
getOperateLog
=
async
()
=>
{
if
(
!
customerId
.
value
)
{
return
}
const
data
=
await
CustomerApi
.
getOperateLogPage
({
pageNo
:
1
,
pageSize
:
10
,
bizId
:
customerId
})
const
data
=
await
CustomerApi
.
getOperateLogPage
(
customerId
.
value
)
logList
.
value
=
data
.
list
}
const
close
=
()
=>
{
delView
(
unref
(
currentRoute
))
// TODO 先返回到客户列表
push
({
name
:
'CrmCustomer'
})
}
/** 初始化 */
const
{
delView
}
=
useTagsViewStore
()
// 视图操作
const
{
currentRoute
}
=
useRouter
()
// 路由
const
{
params
}
=
useRoute
()
onMounted
(()
=>
{
if
(
!
id
)
{
if
(
!
params
.
id
)
{
ElMessage
.
warning
(
'参数错误,客户不能为空!'
)
delView
(
unref
(
currentRoute
)
)
close
(
)
return
}
getCustomer
(
id
)
customerId
.
value
=
params
.
id
as
unknown
as
number
getCustomer
()
})
</
script
>
src/views/crm/customer/index.vue
View file @
bdf95dc0
...
...
@@ -72,10 +72,17 @@
</el-select>
</el-form-item>
<el-form-item>
<el-button
@
click=
"handleQuery"
>
<Icon
class=
"mr-5px"
icon=
"ep:search"
/>
搜索
</el-button>
<el-button
@
click=
"resetQuery"
>
<Icon
class=
"mr-5px"
icon=
"ep:refresh"
/>
重置
</el-button>
<el-button
@
click=
"handleQuery"
>
<Icon
class=
"mr-5px"
icon=
"ep:search"
/>
搜索
</el-button>
<el-button
@
click=
"resetQuery(undefined)"
>
<Icon
class=
"mr-5px"
icon=
"ep:refresh"
/>
重置
</el-button>
<el-button
v-hasPermi=
"['crm:customer:create']"
type=
"primary"
@
click=
"openForm('create')"
>
<Icon
class=
"mr-5px"
icon=
"ep:plus"
/>
新增
<Icon
class=
"mr-5px"
icon=
"ep:plus"
/>
新增
</el-button>
<el-button
v-hasPermi=
"['crm:customer:export']"
...
...
@@ -84,7 +91,8 @@
type=
"success"
@
click=
"handleExport"
>
<Icon
class=
"mr-5px"
icon=
"ep:download"
/>
导出
<Icon
class=
"mr-5px"
icon=
"ep:download"
/>
导出
</el-button>
</el-form-item>
</el-form>
...
...
@@ -92,11 +100,20 @@
<!-- 列表 -->
<ContentWrap>
<!-- TODO @puhui999:是不是就 3 重呀,我负责的,我参与的,我下属的 -->
<el-tabs
v-model=
"activeName"
@
tab-click=
"handleClick"
>
<el-tab-pane
label=
"客户列表"
name=
"1"
/>
<el-tab-pane
label=
"我负责的"
name=
"2"
/>
<el-tab-pane
label=
"我关注的"
name=
"3"
/>
<el-tab-pane
label=
"我参与的"
name=
"4"
/>
<el-tab-pane
label=
"下属负责的"
name=
"5"
/>
<el-tab-pane
label=
"客户公海"
name=
"6"
/>
</el-tabs>
<el-table
v-loading=
"loading"
:data=
"list"
:show-overflow-tooltip=
"true"
:stripe=
"true"
>
<el-table-column
align=
"center"
label=
"编号"
prop=
"id"
/>
<el-table-column
align=
"center"
label=
"客户名称"
prop=
"name"
width=
"160"
>
<template
#
default=
"scope"
>
<el-link
type=
"primary"
:underline=
"false
"
@
click=
"openDetail(scope.row.id)"
>
<el-link
:underline=
"false"
type=
"primary
"
@
click=
"openDetail(scope.row.id)"
>
{{
scope
.
row
.
name
}}
</el-link>
</
template
>
...
...
@@ -197,6 +214,7 @@ import { dateFormatter } from '@/utils/formatTime'
import
download
from
'@/utils/download'
import
*
as
CustomerApi
from
'@/api/crm/customer'
import
CustomerForm
from
'./CustomerForm.vue'
import
{
TabsPaneContext
}
from
'element-plus'
defineOptions
({
name
:
'CrmCustomer'
})
...
...
@@ -206,24 +224,78 @@ const { t } = useI18n() // 国际化
const
loading
=
ref
(
true
)
// 列表的加载中
const
total
=
ref
(
0
)
// 列表的总页数
const
list
=
ref
([])
// 列表的数据
const
queryParams
=
reactive
({
const
queryParams
=
ref
<
{
pageNo
:
number
pageSize
:
number
name
:
string
mobile
:
string
industryId
:
number
|
undefined
level
:
number
|
undefined
source
:
number
|
undefined
sceneType
:
number
|
undefined
pool
:
boolean
|
undefined
}
>
({
pageNo
:
1
,
pageSize
:
10
,
pool
:
false
,
name
:
''
,
mobile
:
''
,
industryId
:
undefined
,
level
:
undefined
,
source
:
undefined
source
:
undefined
,
sceneType
:
undefined
,
pool
:
undefined
})
const
queryFormRef
=
ref
()
// 搜索的表单
const
exportLoading
=
ref
(
false
)
// 导出的加载中
const
activeName
=
ref
(
'1'
)
// 列表 tab
enum
CrmSceneTypeEnum
{
OWNER
=
1
,
FOLLOW
=
2
,
INVOLVED
=
3
,
SUBORDINATE
=
4
}
const
handleClick
=
(
tab
:
TabsPaneContext
)
=>
{
switch
(
tab
.
paneName
)
{
case
'1'
:
resetQuery
()
break
case
'2'
:
resetQuery
(()
=>
{
queryParams
.
value
.
sceneType
=
CrmSceneTypeEnum
.
OWNER
})
break
case
'3'
:
resetQuery
(()
=>
{
queryParams
.
value
.
sceneType
=
CrmSceneTypeEnum
.
FOLLOW
})
break
// TODO @puhui999:这个貌似报错?
case
'4'
:
resetQuery
(()
=>
{
queryParams
.
value
.
sceneType
=
CrmSceneTypeEnum
.
INVOLVED
})
break
case
'5'
:
resetQuery
(()
=>
{
queryParams
.
value
.
sceneType
=
CrmSceneTypeEnum
.
SUBORDINATE
})
break
// TODO @puhui999:公海单独一个菜单哈。
case
'6'
:
resetQuery
(()
=>
{
queryParams
.
value
.
pool
=
true
})
break
}
}
/** 查询列表 */
const
getList
=
async
()
=>
{
loading
.
value
=
true
try
{
const
data
=
await
CustomerApi
.
getCustomerPage
(
queryParams
)
const
data
=
await
CustomerApi
.
getCustomerPage
(
queryParams
.
value
)
list
.
value
=
data
.
list
total
.
value
=
data
.
total
}
finally
{
...
...
@@ -233,19 +305,30 @@ const getList = async () => {
/** 搜索按钮操作 */
const
handleQuery
=
()
=>
{
queryParams
.
pageNo
=
1
queryParams
.
value
.
pageNo
=
1
getList
()
}
/** 重置按钮操作 */
const
resetQuery
=
()
=>
{
const
resetQuery
=
(
func
:
Function
|
undefined
=
undefined
)
=>
{
queryFormRef
.
value
.
resetFields
()
queryParams
.
pool
=
false
queryParams
.
value
=
{
pageNo
:
1
,
pageSize
:
10
,
name
:
''
,
mobile
:
''
,
industryId
:
undefined
,
level
:
undefined
,
source
:
undefined
,
sceneType
:
undefined
,
pool
:
undefined
}
func
&&
func
()
handleQuery
()
}
/** 打开客户详情 */
const
{
push
}
=
useRouter
()
const
{
currentRoute
,
push
}
=
useRouter
()
const
openDetail
=
(
id
:
number
)
=>
{
push
({
name
:
'CrmCustomerDetail'
,
params
:
{
id
}
})
}
...
...
@@ -276,7 +359,7 @@ const handleExport = async () => {
await
message
.
exportConfirm
()
// 发起导出
exportLoading
.
value
=
true
const
data
=
await
CustomerApi
.
exportCustomer
(
queryParams
)
const
data
=
await
CustomerApi
.
exportCustomer
(
queryParams
.
value
)
download
.
excel
(
data
,
'客户.xls'
)
}
catch
{
}
finally
{
...
...
@@ -284,6 +367,14 @@ const handleExport = async () => {
}
}
/** 监听路由变化更新列表 */
watch
(
()
=>
currentRoute
.
value
,
()
=>
{
getList
()
}
)
/** 初始化 **/
onMounted
(()
=>
{
getList
()
...
...
src/views/crm/permission/components/PermissionList.vue
View file @
bdf95dc0
...
...
@@ -2,7 +2,8 @@
<!-- 操作栏 -->
<el-row
justify=
"end"
>
<el-button
@
click=
"openForm"
>
<Icon
class=
"mr-5px"
icon=
"fluent:people-team-add-20-filled"
/>
添加团队成员
<Icon
class=
"mr-5px"
icon=
"fluent:people-team-add-20-filled"
/>
添加团队成员
</el-button>
<el-button
@
click=
"handleUpdate"
>
<Icon
class=
"mr-5px"
icon=
"ep:edit"
/>
...
...
@@ -105,14 +106,14 @@ const handleDelete = async () => {
message
.
warning
(
'请先选择团队成员后操作!'
)
return
}
// TODO @puhui999:应该有个提示哈
await
message
.
delConfirm
()
await
message
.
delConfirm
(
'是否删除选择的团队成员?'
)
const
ids
=
multipleSelection
.
value
?.
map
((
item
)
=>
item
.
id
)
await
PermissionApi
.
deletePermissionBatch
({
bizType
:
props
.
bizType
,
bizId
:
props
.
bizId
,
ids
})
message
.
success
(
'删除成功'
)
}
/** 退出团队 */
...
...
@@ -125,7 +126,7 @@ const handleQuit = async () => {
message
.
warning
(
'负责人不能退出团队!'
)
return
}
// TODO @puhui999:应该有个提示哈
await
message
.
confirm
(
'确认退出团队吗?'
)
const
userPermission
=
list
.
value
.
find
((
item
)
=>
item
.
userId
===
userStore
.
getUser
.
id
)
await
PermissionApi
.
deleteSelfPermission
(
userPermission
?.
id
)
}
...
...
@@ -133,7 +134,10 @@ const handleQuit = async () => {
/** 监听打开的 bizId + bizType,从而加载最新的列表 */
watch
(
()
=>
[
props
.
bizId
,
props
.
bizType
],
()
=>
{
(
val
)
=>
{
if
(
!
val
[
0
])
{
return
}
getList
()
},
{
immediate
:
true
,
deep
:
true
}
...
...
src/views/mall/product/spu/index.vue
View file @
bdf95dc0
<!-- 商品中心 - 商品列表 -->
<
template
>
<!-- 搜索工作栏 -->
<ContentWrap>
...
...
@@ -125,27 +126,33 @@
</el-form>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"商品编号"
min-width=
"6
0"
prop=
"id"
/>
<el-table-column
label=
"商品
图"
min-width=
"8
0"
>
<el-table-column
label=
"商品编号"
min-width=
"14
0"
prop=
"id"
/>
<el-table-column
label=
"商品
信息"
min-width=
"30
0"
>
<
template
#
default=
"{ row }"
>
<el-image
:src=
"row.picUrl"
class=
"h-30px w-30px"
@
click=
"imagePreview(row.picUrl)"
/>
<div
class=
"flex"
>
<el-image
fit=
"cover"
:src=
"row.picUrl"
class=
"flex-none w-50px h-50px"
@
click=
"imagePreview(row.picUrl)"
/>
<div
class=
"ml-4 overflow-hidden"
>
<el-tooltip
effect=
"dark"
:content=
"row.name"
placement=
"top"
>
<div>
{{
row
.
name
}}
</div>
</el-tooltip>
</div>
</div>
</
template
>
</el-table-column>
<el-table-column
:show-overflow-tooltip=
"true"
label=
"商品名称"
min-width=
"300"
prop=
"name"
/>
<el-table-column
align=
"center"
label=
"商品售价"
min-width=
"90"
prop=
"price"
>
<
template
#
default=
"{ row }"
>
{{
fenToYuan
(
row
.
price
)
}}
元
</
template
>
<el-table-column
align=
"center"
label=
"价格"
min-width=
"160"
prop=
"price"
>
<
template
#
default=
"{ row }"
>
¥
{{
fenToYuan
(
row
.
price
)
}}
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"销量"
min-width=
"90"
prop=
"salesCount"
/>
<el-table-column
align=
"center"
label=
"库存"
min-width=
"90"
prop=
"stock"
/>
<el-table-column
align=
"center"
label=
"排序"
min-width=
"70"
prop=
"sort"
/>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"创建时间"
prop=
"createTime"
width=
"180"
/>
<el-table-column
align=
"center"
label=
"状态"
min-width=
"80"
>
<el-table-column
align=
"center"
label=
"销售状态"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
<template
v-if=
"row.status >= 0"
>
<el-switch
...
...
@@ -163,16 +170,16 @@
</
template
>
</template>
</el-table-column>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"创建时间"
prop=
"createTime"
width=
"180"
/>
<el-table-column
align=
"center"
fixed=
"right"
label=
"操作"
min-width=
"200"
>
<
template
#
default=
"{ row }"
>
<el-button
v-hasPermi=
"['product:spu:update']"
link
type=
"primary"
@
click=
"openDetail(row.id)"
>
详情
</el-button>
<el-button
link
type=
"primary"
@
click=
"openDetail(row.id)"
>
详情
</el-button>
<el-button
v-hasPermi=
"['product:spu:update']"
link
...
...
@@ -196,17 +203,17 @@
type=
"primary"
@
click=
"handleStatus02Change(row, ProductSpuStatusEnum.DISABLE.status)"
>
恢复
到仓库
恢复
</el-button>
</
template
>
<
template
v-else
>
<el-button
v-hasPermi=
"['product:spu:update']"
link
type=
"
primary
"
type=
"
danger
"
@
click=
"handleStatus02Change(row, ProductSpuStatusEnum.RECYCLE.status)"
>
加入回收站
回收
</el-button>
</
template
>
</template>
...
...
@@ -236,48 +243,41 @@ defineOptions({ name: 'ProductSpu' })
const
message
=
useMessage
()
// 消息弹窗
const
{
t
}
=
useI18n
()
// 国际化
const
{
currentRoute
,
push
}
=
useRouter
()
// 路由跳转
const
{
push
}
=
useRouter
()
// 路由跳转
const
loading
=
ref
(
false
)
// 列表的加载中
const
exportLoading
=
ref
(
false
)
// 导出的加载中
const
total
=
ref
(
0
)
// 列表的总页数
const
list
=
ref
<
any
[]
>
([])
// 列表的数据
const
list
=
ref
<
ProductSpuApi
.
Spu
[]
>
([])
// 列表的数据
// tabs 数据
const
tabsData
=
ref
([
{
count
:
0
,
name
:
'出售中商品'
,
type
:
0
name
:
'出售中'
,
type
:
0
,
count
:
0
},
{
count
:
0
,
name
:
'仓库中商品'
,
type
:
1
name
:
'仓库中'
,
type
:
1
,
count
:
0
},
{
count
:
0
,
name
:
'已售罄商品'
,
type
:
2
name
:
'已售罄'
,
type
:
2
,
count
:
0
},
{
count
:
0
,
name
:
'警戒库存'
,
type
:
3
type
:
3
,
count
:
0
},
{
count
:
0
,
name
:
'商品回收站'
,
type
:
4
name
:
'回收站'
,
type
:
4
,
count
:
0
}
])
/** 获得每个 Tab 的数量 */
const
getTabsCount
=
async
()
=>
{
const
res
=
await
ProductSpuApi
.
getTabsCount
()
for
(
let
objName
in
res
)
{
tabsData
.
value
[
Number
(
objName
)].
count
=
res
[
objName
]
}
}
const
queryParams
=
ref
({
pageNo
:
1
,
pageSize
:
10
,
...
...
@@ -288,11 +288,6 @@ const queryParams = ref({
})
// 查询参数
const
queryFormRef
=
ref
()
// 搜索的表单Ref
const
handleTabClick
=
(
tab
:
TabsPaneContext
)
=>
{
queryParams
.
value
.
tabType
=
tab
.
paneName
as
number
getList
()
}
/** 查询列表 */
const
getList
=
async
()
=>
{
loading
.
value
=
true
...
...
@@ -305,8 +300,22 @@ const getList = async () => {
}
}
/** 切换 Tab */
const
handleTabClick
=
(
tab
:
TabsPaneContext
)
=>
{
queryParams
.
value
.
tabType
=
tab
.
paneName
as
number
getList
()
}
/** 获得每个 Tab 的数量 */
const
getTabsCount
=
async
()
=>
{
const
res
=
await
ProductSpuApi
.
getTabsCount
()
for
(
let
objName
in
res
)
{
tabsData
.
value
[
Number
(
objName
)].
count
=
res
[
objName
]
}
}
/** 添加到仓库 / 回收站的状态 */
const
handleStatus02Change
=
async
(
row
,
newStatus
:
number
)
=>
{
const
handleStatus02Change
=
async
(
row
:
any
,
newStatus
:
number
)
=>
{
try
{
// 二次确认
const
text
=
newStatus
===
ProductSpuStatusEnum
.
RECYCLE
.
status
?
'加入到回收站'
:
'恢复到仓库'
...
...
@@ -322,7 +331,7 @@ const handleStatus02Change = async (row, newStatus: number) => {
}
/** 更新上架/下架状态 */
const
handleStatusChange
=
async
(
row
)
=>
{
const
handleStatusChange
=
async
(
row
:
any
)
=>
{
try
{
// 二次确认
const
text
=
row
.
status
?
'上架'
:
'下架'
...
...
@@ -407,19 +416,16 @@ const handleExport = async () => {
}
}
const
categoryList
=
ref
()
// 分类树
/** 获取分类的节点的完整结构 */
const
formatCategoryName
=
(
categoryId
)
=>
{
const
categoryList
=
ref
()
// 分类树
const
formatCategoryName
=
(
categoryId
:
number
)
=>
{
return
treeToString
(
categoryList
.
value
,
categoryId
)
}
// 监听路由变化更新列表,解决商品保存后,列表不刷新的问题。
watch
(
()
=>
currentRoute
.
value
,
()
=>
{
getList
()
}
)
/** 激活时 */
onActivated
(()
=>
{
getList
()
})
/** 初始化 **/
onMounted
(
async
()
=>
{
...
...
src/views/mall/promotion/rewardActivity/RewardForm.vue
View file @
bdf95dc0
...
...
@@ -24,22 +24,96 @@
<el-radio
v-for=
"dict in getIntDictOptions(DICT_TYPE.PROMOTION_CONDITION_TYPE)"
:key=
"dict.value"
:label=
"parseInt(dict.value)"
>
{{
dict
.
label
}}
</el-radio
:label=
"dict.value"
>
{{
dict
.
label
}}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
label=
"优惠设置"
>
<!-- TODO 待实现!这个实现下哈 -->
<template
v-for=
"(item, index) in formData.rules"
:key=
"index"
>
<el-row
type=
"flex"
>
<el-col
:span=
"24"
style=
"font-weight: bold; display: flex"
>
活动层级
{{
index
+
1
}}
<el-button
link
type=
"danger"
style=
"margin-left: auto"
v-if=
"index != 0"
@
click=
"deleteActivityRule(index)"
>
删除
</el-button>
</el-col>
<e-form
:ref=
"'formRef' + index"
:model=
"item"
>
<el-form-item
label=
"优惠门槛:"
prop=
"limit"
label-width=
"100px"
style=
"padding-left: 50px"
>
满
<el-input
style=
"width: 150px; padding: 0 10px"
v-model=
"item.limit"
type=
"number"
placeholder=
""
/>
元
</el-form-item>
<el-form-item
label=
"优惠内容:"
label-width=
"100px"
style=
"padding-left: 50px"
>
<el-checkbox-group
v-model=
"activityRules[index]"
style=
"width: 100%"
>
<el-col
:span=
"24"
>
<el-checkbox
label=
"订单金额优惠"
name=
"type"
/>
<el-form-item
v-if=
"activityRules[index].includes('订单金额优惠')"
>
减
<el-input
style=
"width: 150px; padding: 0 20px"
v-model=
"item.discountPrice"
type=
"number"
placeholder=
""
/>
元
</el-form-item>
</el-col>
<el-col
:span=
"24"
>
<el-checkbox
v-model=
"item.freeDelivery"
label=
"包邮"
name=
"type"
/>
</el-col>
<el-col
:span=
"24"
>
<el-checkbox
label=
"送积分"
name=
"type"
/>
<el-form-item
v-if=
"activityRules[index].includes('送积分')"
>
送
<el-input
style=
"width: 150px; padding: 0 20px"
v-model=
"item.point"
type=
"number"
placeholder=
""
/>
积分
</el-form-item>
</el-col>
<!-- 优惠券待处理 也可以参考优惠劵的SpuShowcase-->
<!-- TODO 待实现!-->
<el-col
:span=
"24"
>
<el-checkbox
label=
"送优惠券"
name=
"type"
/>
</el-col>
</el-checkbox-group>
</el-form-item>
</e-form>
</el-row>
</
template
>
<!-- TODO 实现:建议改成放在每一个【活动层级】的下面,有点类似主子表 -->
<el-button
type=
"primary"
@
click=
"addActivityStratum"
>
添加活动层级
</el-button>
</el-form-item>
<el-form-item
label=
"活动商品"
prop=
"productScope"
>
<el-radio-group
v-model=
"formData.productScope"
>
<el-radio
v-for=
"dict in getIntDictOptions(DICT_TYPE.PROMOTION_PRODUCT_SCOPE)"
:key=
"dict.value"
:label=
"parseInt(dict.value)"
>
{{
dict
.
label
}}
</el-radio
:label=
"dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<!-- TODO:活动商品的开发,可以参考优惠劵的,已经搞好啦; -->
...
...
@@ -58,9 +132,9 @@
>
<el-option
v-for=
"item in productSpus"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
>
<span
style=
"float: left"
>
{{ item.name }}
</span>
<span
style=
"float: right; font-size: 13px; color: #8492a6"
>
¥
{{
(
item
.
price
/
100.0
).
toFixed
(
2
)
}}
</span
>
<span
style=
"float: right; font-size: 13px; color: #8492a6"
>
¥{{ (item.price / 100.0).toFixed(2) }}
</span
>
</el-option>
</el-select>
</el-form-item>
...
...
@@ -77,15 +151,8 @@
<
script
lang=
"ts"
setup
>
import
{
getSpuSimpleList
}
from
'@/api/mall/product/spu'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
CommonStatusEnum
}
from
'@/utils/constants'
import
*
as
ProductBrandApi
from
'@/api/mall/product/brand'
import
{
PromotionConditionTypeEnum
,
PromotionProductScopeEnum
,
PromotionActivityStatusEnum
}
from
'@/utils/constants'
// 商品数据
const
productSpus
=
ref
<
any
[]
>
([])
import
*
as
RewardActivityApi
from
'@/api/mall/promotion/reward/rewardActivity'
import
{
PromotionConditionTypeEnum
,
PromotionProductScopeEnum
}
from
'@/utils/constants'
/** 初始化 **/
onMounted
(()
=>
{
...
...
@@ -98,6 +165,7 @@ defineOptions({ name: 'ProductBrandForm' })
const
{
t
}
=
useI18n
()
// 国际化
const
message
=
useMessage
()
// 消息弹窗
const
productSpus
=
ref
<
any
[]
>
([])
// 商品数据
const
dialogVisible
=
ref
(
false
)
// 弹窗的是否展示
const
dialogTitle
=
ref
(
''
)
// 弹窗的标题
const
formLoading
=
ref
(
false
)
// 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
...
...
@@ -112,8 +180,18 @@ const formData = ref({
remark
:
undefined
,
productScope
:
PromotionProductScopeEnum
.
ALL
.
scope
,
productSpuIds
:
undefined
,
rules
:
undefined
rules
:
[
{
limit
:
undefined
,
discountPrice
:
undefined
,
freeDelivery
:
undefined
,
point
:
undefined
,
couponIds
:
[],
couponCounts
:
[]
}
]
})
const
activityRules
=
reactive
([])
// 优惠设置。每个元素都是一个 [],放“包邮”、“送积分”、“订单金额优惠”
const
formRules
=
reactive
({
name
:
[{
required
:
true
,
message
:
'活动名称不能为空'
,
trigger
:
'blur'
}],
startAndEndTime
:
[{
required
:
true
,
message
:
'活动时间不能为空'
,
trigger
:
'blur'
}],
...
...
@@ -121,7 +199,7 @@ const formRules = reactive({
productScope
:
[{
required
:
true
,
message
:
'商品范围不能为空'
,
trigger
:
'blur'
}],
productSpuIds
:
[{
required
:
true
,
message
:
'商品范围不能为空'
,
trigger
:
'blur'
}]
})
const
formRef
=
ref
()
// 表单 Ref
const
formRef
=
ref
(
[]
)
// 表单 Ref
/** 打开弹窗 */
const
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
...
...
@@ -133,19 +211,24 @@ const open = async (type: string, id?: number) => {
if
(
id
)
{
formLoading
.
value
=
true
try
{
// formData.value = await ProductBrandApi.getBrand(id)
formData
.
value
=
{
conditionType
:
10
,
description
:
''
,
id
:
undefined
,
name
:
'测试活动'
,
picUrl
:
''
,
productScope
:
2
,
productSpuIds
:
[
634
],
remark
:
'测试备注'
,
startAndEndTime
:
[
new
Date
(),
new
Date
(
'2023-12-31'
)],
status
:
0
}
let
data
=
await
RewardActivityApi
.
getReward
(
id
)
data
.
startAndEndTime
=
[
new
Date
(
data
.
startTime
),
new
Date
(
data
.
endTime
)]
activityRules
.
splice
(
0
,
activityRules
.
length
)
data
.
rules
.
forEach
((
item
)
=>
{
// TODO 是不是不用 reactive,直接 [] 就可以了?
let
array
:
string
[]
=
reactive
([])
if
(
item
.
freeDelivery
)
{
array
.
push
(
'包邮'
)
}
if
(
item
.
point
)
{
array
.
push
(
'送积分'
)
}
if
(
item
.
discountPrice
)
{
array
.
push
(
'订单金额优惠'
)
}
activityRules
.
push
(
array
)
})
formData
.
value
=
data
}
finally
{
formLoading
.
value
=
false
}
...
...
@@ -160,18 +243,28 @@ const submitForm = async () => {
if
(
!
formRef
)
return
const
valid
=
await
formRef
.
value
.
validate
()
if
(
!
valid
)
return
console
.
log
(
formData
.
value
)
message
.
success
(
'已在控制台打印数据'
)
return
// 处理下数据兼容接口
formData
.
value
.
startTime
=
+
new
Date
(
formData
.
value
.
startAndEndTime
[
0
])
formData
.
value
.
endTime
=
+
new
Date
(
formData
.
value
.
startAndEndTime
[
1
])
activityRules
.
forEach
((
item
,
index
)
=>
{
formData
.
value
.
rules
[
index
].
freeDelivery
=
!!
item
.
includes
(
'包邮'
)
if
(
!
item
.
includes
(
'送积分'
))
{
formData
.
value
.
rules
[
index
].
point
=
undefined
}
if
(
!
item
.
includes
(
'订单金额优惠'
))
{
formData
.
value
.
rules
[
index
].
discountPrice
=
undefined
}
})
// 提交请求
formLoading
.
value
=
true
try
{
const
data
=
formData
.
value
as
ProductBrandApi
.
Brand
VO
const
data
=
formData
.
value
as
RewardActivityApi
.
DiscountActivity
VO
if
(
formType
.
value
===
'create'
)
{
await
ProductBrandApi
.
createBrand
(
data
)
await
RewardActivityApi
.
createRewardActivity
(
data
)
message
.
success
(
t
(
'common.createSuccess'
))
}
else
{
await
ProductBrandApi
.
updateBrand
(
data
)
await
RewardActivityApi
.
updateRewardActivity
(
data
)
message
.
success
(
t
(
'common.updateSuccess'
))
}
dialogVisible
.
value
=
false
...
...
@@ -182,15 +275,51 @@ const submitForm = async () => {
}
}
const
addActivityStratum
=
()
=>
{
formData
.
value
.
rules
.
push
({
limit
:
undefined
,
discountPrice
:
undefined
,
freeDelivery
:
undefined
,
point
:
undefined
,
couponIds
:
[],
couponCounts
:
[]
})
activityRules
.
push
([])
}
const
deleteActivityRule
=
(
index
)
=>
{
formData
.
value
.
rules
.
splice
(
index
,
1
)
activityRules
.
splice
(
index
,
1
)
}
/** 重置表单 */
const
resetForm
=
()
=>
{
formData
.
value
=
{
id
:
undefined
,
name
:
''
,
picUrl
:
''
,
status
:
CommonStatusEnum
.
ENABLE
,
description
:
''
name
:
undefined
,
startAndEndTime
:
undefined
,
startTime
:
undefined
,
endTime
:
undefined
,
conditionType
:
PromotionConditionTypeEnum
.
PRICE
.
type
,
remark
:
undefined
,
productScope
:
PromotionProductScopeEnum
.
ALL
.
scope
,
productSpuIds
:
undefined
,
rules
:
[
{
limit
:
undefined
,
discountPrice
:
undefined
,
freeDelivery
:
undefined
,
point
:
undefined
,
couponIds
:
[],
couponCounts
:
[]
}
]
}
formRef
.
value
?.
resetFields
()
activityRules
.
splice
(
0
,
activityRules
.
length
)
activityRules
.
push
(
reactive
([]))
// 解决下有时刷新页面第一次点编辑报错
nextTick
(()
=>
{
formRef
.
value
?.
resetFields
()
})
}
</
script
>
src/views/mall/promotion/rewardActivity/index.vue
View file @
bdf95dc0
...
...
@@ -65,13 +65,13 @@
<el-table-column
label=
"活动开始时间"
align=
"center"
prop=
"s
ort[0]
"
prop=
"s
tartTime
"
:formatter=
"dateFormatter"
/>
<el-table-column
label=
"活动结束时间"
align=
"center"
prop=
"
sort[1]
"
prop=
"
endTime
"
:formatter=
"dateFormatter"
/>
<el-table-column
label=
"状态"
align=
"center"
prop=
"status"
>
...
...
@@ -122,7 +122,7 @@
<
script
lang=
"ts"
setup
>
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
*
as
ProductBrandApi
from
'@/api/mall/product/brand
'
import
*
as
RewardActivityApi
from
'@/api/mall/promotion/reward/rewardActivity
'
import
RewardForm
from
'./RewardForm.vue'
defineOptions
({
name
:
'PromotionRewardActivity'
})
...
...
@@ -146,22 +146,7 @@ const queryFormRef = ref() // 搜索的表单
const
getList
=
async
()
=>
{
loading
.
value
=
true
try
{
// const data = await ProductBrandApi.getBrandParam(queryParams)
const
data
=
{
list
:
[
{
createTime
:
1693463998000
,
description
:
''
,
id
:
3
,
name
:
'索尼'
,
picUrl
:
'http://127.0.0.1:48080/admin-api/infra/file/4/get/f5b7a536306cd1180a42a2211a8212dc23de6b949d30c30d036caa063042f928.png'
,
sort
:
[
+
new
Date
(),
+
new
Date
(
'2023-12-31'
)],
status
:
10
}
],
total
:
1
}
const
data
=
await
RewardActivityApi
.
getRewardActivityPage
(
queryParams
)
list
.
value
=
data
.
list
total
.
value
=
data
.
total
}
finally
{
...
...
@@ -171,16 +156,11 @@ const getList = async () => {
/** 搜索按钮操作 */
const
handleQuery
=
()
=>
{
console
.
log
(
queryParams
)
message
.
success
(
'已打印搜索参数'
)
return
getList
()
}
/** 重置按钮操作 */
const
resetQuery
=
()
=>
{
message
.
success
(
'重置查询表单获取数据'
)
return
queryFormRef
.
value
.
resetFields
()
handleQuery
()
}
...
...
@@ -196,10 +176,8 @@ const handleDelete = async (id: number) => {
try
{
// 删除的二次确认
await
message
.
delConfirm
()
message
.
success
(
'您以确认删除'
)
return
// 发起删除
await
ProductBrandApi
.
deleteBrand
(
id
)
await
RewardActivityApi
.
deleteRewardActivity
(
id
)
message
.
success
(
t
(
'common.delSuccess'
))
// 刷新列表
await
getList
()
...
...
src/views/mall/statistics/product/components/ProductRank.vue
View file @
bdf95dc0
...
...
@@ -77,12 +77,9 @@ const queryParams = reactive({
times
:
[],
sortingFields
:
{}
})
// 列表的加载中
const
loading
=
ref
(
false
)
// 列表的总页数
const
total
=
ref
(
0
)
// 列表的数据
const
list
=
ref
<
ProductStatisticsVO
[]
>
([])
const
loading
=
ref
(
false
)
// 列表的加载中
const
total
=
ref
(
0
)
// 列表的总页数
const
list
=
ref
<
ProductStatisticsVO
[]
>
([])
// 列表的数据
/** 查询商品列表 */
const
getSpuList
=
async
()
=>
{
...
...
src/views/mall/trade/config/index.vue
View file @
bdf95dc0
...
...
@@ -186,17 +186,6 @@
</el-checkbox-group>
<el-text
class=
"w-full"
size=
"small"
type=
"info"
>
商城开通提现的付款方式
</el-text>
</el-form-item>
<el-form-item
label=
"提现银行"
prop=
"brokerageBankNames"
>
<el-select
v-model=
"formData.brokerageBankNames"
placeholder=
"请选择提现银行"
multiple
>
<el-option
v-for=
"dict in getIntDictOptions(DICT_TYPE.BROKERAGE_BANK_NAME)"
:key=
"dict.value"
:label=
"dict.label"
:value=
"dict.value"
/>
</el-select>
<el-text
class=
"w-full"
size=
"small"
type=
"info"
>
商城开通提现的银行列表
</el-text>
</el-form-item>
</el-tab-pane>
</el-tabs>
<!-- 保存 -->
...
...
@@ -232,7 +221,6 @@ const formData = ref({
brokerageSecondPercent
:
0
,
brokerageWithdrawMinPrice
:
0
,
brokerageWithdrawFeePercent
:
0
,
brokerageBankNames
:
[],
brokerageFrozenDays
:
0
,
brokerageWithdrawTypes
:
[]
})
...
...
@@ -246,7 +234,6 @@ const formRules = reactive({
{
required
:
true
,
message
:
'用户提现最低金额不能为空'
,
trigger
:
'blur'
}
],
brokerageWithdrawFeePercent
:
[{
required
:
true
,
message
:
'提现手续费不能为空'
,
trigger
:
'blur'
}],
brokerageBankNames
:
[{
required
:
true
,
message
:
'提现银行不能为空'
,
trigger
:
'blur'
}],
brokerageFrozenDays
:
[{
required
:
true
,
message
:
'佣金冻结时间不能为空'
,
trigger
:
'blur'
}],
brokerageWithdrawTypes
:
[
{
...
...
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