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
f545dcc6
authored
Sep 10, 2024
by
xunchangguo@126.com
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/master'
parents
09d1dbed
6f47069d
Show whitespace changes
Inline
Side-by-side
Showing
76 changed files
with
2684 additions
and
894 deletions
+2684
-894
pnpm-lock.yaml
+0
-0
src/api/ai/mindmap/index.ts
+1
-1
src/api/mall/promotion/combination/combinationActivity.ts
+7
-1
src/api/mall/promotion/coupon/couponTemplate.ts
+1
-1
src/api/mall/promotion/reward/rewardActivity.ts
+15
-10
src/api/mall/promotion/seckill/seckillActivity.ts
+6
-0
src/api/member/user/index.ts
+0
-5
src/api/pay/wallet/balance/index.ts
+8
-2
src/components/DictTag/src/DictTag.vue
+2
-2
src/components/DiyEditor/components/mobile/FloatingActionButton/index.vue
+1
-1
src/components/DiyEditor/components/mobile/ProductCard/index.vue
+4
-3
src/components/DiyEditor/components/mobile/ProductList/index.vue
+2
-1
src/components/DiyEditor/components/mobile/PromotionCombination/config.ts
+36
-4
src/components/DiyEditor/components/mobile/PromotionCombination/index.vue
+136
-70
src/components/DiyEditor/components/mobile/PromotionCombination/property.vue
+68
-16
src/components/DiyEditor/components/mobile/PromotionSeckill/config.ts
+37
-5
src/components/DiyEditor/components/mobile/PromotionSeckill/index.vue
+136
-70
src/components/DiyEditor/components/mobile/PromotionSeckill/property.vue
+68
-16
src/components/Icon/src/IconSelect.vue
+11
-1
src/components/UploadFile/src/UploadFile.vue
+20
-4
src/config/axios/service.ts
+1
-4
src/layout/components/Setting/src/Setting.vue
+1
-1
src/layout/components/TagsView/src/TagsView.vue
+32
-26
src/layout/components/UserInfo/src/UserInfo.vue
+1
-1
src/layout/components/UserInfo/src/components/LockDialog.vue
+1
-1
src/layout/components/UserInfo/src/components/LockPage.vue
+1
-1
src/router/index.ts
+1
-1
src/store/modules/app.ts
+1
-1
src/utils/is.ts
+2
-2
src/views/Login/Login.vue
+4
-3
src/views/Login/SocialLogin.vue
+3
-3
src/views/Login/components/QrCodeForm.vue
+1
-1
src/views/ai/chat/index/components/message/MessageList.vue
+1
-1
src/views/ai/mindmap/index/components/Right.vue
+7
-3
src/views/ai/mindmap/index/index.vue
+2
-2
src/views/ai/write/index/components/Left.vue
+17
-17
src/views/bpm/processInstance/detail/ProcessInstanceOperationButton.vue
+3
-10
src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue
+161
-0
src/views/bpm/processInstance/detail/index_new.vue
+9
-50
src/views/mall/home/components/OperationDataCard.vue
+2
-2
src/views/mall/product/spu/components/SkuList.vue
+9
-9
src/views/mall/product/spu/components/SpuShowcase.vue
+2
-0
src/views/mall/product/spu/form/ProductAttributes.vue
+5
-5
src/views/mall/product/spu/form/ProductPropertyAddForm.vue
+3
-3
src/views/mall/product/spu/form/SkuForm.vue
+1
-1
src/views/mall/promotion/combination/components/CombinationShowcase.vue
+158
-0
src/views/mall/promotion/combination/components/CombinationTableSelect.vue
+345
-0
src/views/mall/promotion/components/SpuAndSkuList.vue
+12
-13
src/views/mall/promotion/coupon/components/CouponSelect.vue
+13
-41
src/views/mall/promotion/discountActivity/DiscountActivityForm.vue
+33
-20
src/views/mall/promotion/kefu/components/KeFuMessageList.vue
+3
-3
src/views/mall/promotion/kefu/components/tools/emoji.ts
+5
-2
src/views/mall/promotion/rewardActivity/RewardForm.vue
+90
-202
src/views/mall/promotion/rewardActivity/components/RewardRule.vue
+127
-0
src/views/mall/promotion/rewardActivity/components/RewardRuleCouponSelect.vue
+136
-0
src/views/mall/promotion/rewardActivity/index.vue
+37
-30
src/views/mall/promotion/seckill/components/SeckillShowcase.vue
+156
-0
src/views/mall/promotion/seckill/components/SeckillTableSelect.vue
+343
-0
src/views/mall/trade/afterSale/index.vue
+1
-1
src/views/member/user/components/UserBalanceUpdateForm.vue
+144
-0
src/views/member/user/components/UserLevelUpdateForm.vue
+0
-0
src/views/member/user/components/UserPointUpdateForm.vue
+8
-7
src/views/member/user/components/balance-list.vue
+0
-14
src/views/member/user/detail/UserAccountInfo.vue
+11
-11
src/views/member/user/detail/UserAftersaleList.vue
+9
-8
src/views/member/user/detail/UserBalanceList.vue
+12
-13
src/views/member/user/detail/index.vue
+7
-7
src/views/member/user/index.vue
+8
-4
src/views/pay/app/components/channel/AlipayChannelForm.vue
+57
-54
src/views/pay/app/components/channel/MockChannelForm.vue
+3
-3
src/views/pay/app/components/channel/WalletChannelForm.vue
+3
-3
src/views/pay/app/components/channel/WeixinChannelForm.vue
+33
-33
src/views/pay/app/index.vue
+42
-42
src/views/pay/wallet/transaction/WalletTransactionList.vue
+24
-13
src/views/system/menu/index.vue
+27
-2
vite.config.ts
+7
-7
No files found.
pnpm-lock.yaml
View file @
f545dcc6
This source diff could not be displayed because it is too large. You can
view the blob
instead.
src/api/ai/mindmap/index.ts
View file @
f545dcc6
import
{
getAccessToken
}
from
'@/utils/auth'
import
{
getAccessToken
}
from
'@/utils/auth'
import
{
fetchEventSource
}
from
'@microsoft/fetch-event-source'
import
{
fetchEventSource
}
from
'@microsoft/fetch-event-source'
import
{
config
}
from
'@/config/axios/config'
import
{
config
}
from
'@/config/axios/config'
import
request
from
'@/config/axios'
import
request
from
'@/config/axios'
// AI 思维导图 VO
// AI 思维导图 VO
// AI 思维导图 VO
export
interface
MindMapVO
{
export
interface
MindMapVO
{
...
...
src/api/mall/promotion/combination/combinationActivity.ts
View file @
f545dcc6
...
@@ -16,6 +16,7 @@ export interface CombinationActivityVO {
...
@@ -16,6 +16,7 @@ export interface CombinationActivityVO {
virtualGroup
?:
number
virtualGroup
?:
number
status
?:
number
status
?:
number
limitDuration
?:
number
limitDuration
?:
number
combinationPrice
?:
number
products
:
CombinationProductVO
[]
products
:
CombinationProductVO
[]
}
}
...
@@ -36,7 +37,7 @@ export interface SpuExtension extends Spu {
...
@@ -36,7 +37,7 @@ export interface SpuExtension extends Spu {
}
}
// 查询拼团活动列表
// 查询拼团活动列表
export
const
getCombinationActivityPage
=
async
(
params
)
=>
{
export
const
getCombinationActivityPage
=
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
'/promotion/combination-activity/page'
,
params
})
return
await
request
.
get
({
url
:
'/promotion/combination-activity/page'
,
params
})
}
}
...
@@ -45,6 +46,11 @@ export const getCombinationActivity = async (id: number) => {
...
@@ -45,6 +46,11 @@ export const getCombinationActivity = async (id: number) => {
return
await
request
.
get
({
url
:
'/promotion/combination-activity/get?id='
+
id
})
return
await
request
.
get
({
url
:
'/promotion/combination-activity/get?id='
+
id
})
}
}
// 获得拼团活动列表,基于活动编号数组
export
const
getCombinationActivityListByIds
=
(
ids
:
number
[])
=>
{
return
request
.
get
({
url
:
`/promotion/combination-activity/list-by-ids?ids=
${
ids
}
`
})
}
// 新增拼团活动
// 新增拼团活动
export
const
createCombinationActivity
=
async
(
data
:
CombinationActivityVO
)
=>
{
export
const
createCombinationActivity
=
async
(
data
:
CombinationActivityVO
)
=>
{
return
await
request
.
post
({
url
:
'/promotion/combination-activity/create'
,
data
})
return
await
request
.
post
({
url
:
'/promotion/combination-activity/create'
,
data
})
...
...
src/api/mall/promotion/coupon/couponTemplate.ts
View file @
f545dcc6
...
@@ -74,7 +74,7 @@ export function getCouponTemplatePage(params: PageParam) {
...
@@ -74,7 +74,7 @@ export function getCouponTemplatePage(params: PageParam) {
}
}
// 获得优惠劵模板分页
// 获得优惠劵模板分页
export
function
getCouponTemplateList
(
ids
:
number
[])
{
export
function
getCouponTemplateList
(
ids
:
number
[])
:
Promise
<
CouponTemplateVO
[]
>
{
return
request
.
get
({
return
request
.
get
({
url
:
`/promotion/coupon-template/list?ids=
${
ids
}
`
url
:
`/promotion/coupon-template/list?ids=
${
ids
}
`
})
})
...
...
src/api/mall/promotion/reward/rewardActivity.ts
View file @
f545dcc6
import
request
from
'@/config/axios'
import
request
from
'@/config/axios'
export
interface
Discount
ActivityVO
{
export
interface
Reward
ActivityVO
{
id
?:
number
id
?:
number
name
?:
string
name
?:
string
startTime
?:
Date
startTime
?:
Date
endTime
?:
Date
endTime
?:
Date
startAndEndTime
?:
Date
[]
// 只前端使用
remark
?:
string
remark
?:
string
conditionType
?:
number
conditionType
?:
number
productScope
?:
number
productScope
?:
number
rules
:
RewardRule
[]
// 如下仅用于表单,不提交
productScopeValues
?:
number
[]
// 商品范围:值为品类编号列表、商品编号列表
productCategoryIds
?:
number
[]
productSpuIds
?:
number
[]
productSpuIds
?:
number
[]
rules
?:
DiscountProductVO
[]
}
}
// 优惠规则
// 优惠规则
export
interface
DiscountProductVO
{
export
interface
RewardRule
{
limit
:
number
limit
?
:
number
discountPrice
:
number
discountPrice
?
:
number
freeDelivery
:
boolean
freeDelivery
?
:
boolean
point
:
number
point
:
number
couponIds
:
number
[]
giveCouponTemplateCounts
?:
{
couponCounts
:
number
[]
[
key
:
number
]:
number
}
}
}
// 新增满减送活动
// 新增满减送活动
export
const
createRewardActivity
=
async
(
data
:
Discount
ActivityVO
)
=>
{
export
const
createRewardActivity
=
async
(
data
:
Reward
ActivityVO
)
=>
{
return
await
request
.
post
({
url
:
'/promotion/reward-activity/create'
,
data
})
return
await
request
.
post
({
url
:
'/promotion/reward-activity/create'
,
data
})
}
}
// 更新满减送活动
// 更新满减送活动
export
const
updateRewardActivity
=
async
(
data
:
Discount
ActivityVO
)
=>
{
export
const
updateRewardActivity
=
async
(
data
:
Reward
ActivityVO
)
=>
{
return
await
request
.
put
({
url
:
'/promotion/reward-activity/update'
,
data
})
return
await
request
.
put
({
url
:
'/promotion/reward-activity/update'
,
data
})
}
}
...
...
src/api/mall/promotion/seckill/seckillActivity.ts
View file @
f545dcc6
...
@@ -18,6 +18,7 @@ export interface SeckillActivityVO {
...
@@ -18,6 +18,7 @@ export interface SeckillActivityVO {
singleLimitCount
?:
number
singleLimitCount
?:
number
stock
?:
number
stock
?:
number
totalStock
?:
number
totalStock
?:
number
seckillPrice
?:
number
products
?:
SeckillProductVO
[]
products
?:
SeckillProductVO
[]
}
}
...
@@ -43,6 +44,11 @@ export const getSeckillActivityPage = async (params) => {
...
@@ -43,6 +44,11 @@ export const getSeckillActivityPage = async (params) => {
return
await
request
.
get
({
url
:
'/promotion/seckill-activity/page'
,
params
})
return
await
request
.
get
({
url
:
'/promotion/seckill-activity/page'
,
params
})
}
}
// 查询秒杀活动列表,基于活动编号数组
export
const
getSeckillActivityListByIds
=
(
ids
:
number
[])
=>
{
return
request
.
get
({
url
:
`/promotion/seckill-activity/list-by-ids?ids=
${
ids
}
`
})
}
// 查询秒杀活动详情
// 查询秒杀活动详情
export
const
getSeckillActivity
=
async
(
id
:
number
)
=>
{
export
const
getSeckillActivity
=
async
(
id
:
number
)
=>
{
return
await
request
.
get
({
url
:
'/promotion/seckill-activity/get?id='
+
id
})
return
await
request
.
get
({
url
:
'/promotion/seckill-activity/get?id='
+
id
})
...
...
src/api/member/user/index.ts
View file @
f545dcc6
...
@@ -46,8 +46,3 @@ export const updateUserLevel = async (data: any) => {
...
@@ -46,8 +46,3 @@ export const updateUserLevel = async (data: any) => {
export
const
updateUserPoint
=
async
(
data
:
any
)
=>
{
export
const
updateUserPoint
=
async
(
data
:
any
)
=>
{
return
await
request
.
put
({
url
:
`/member/user/update-point`
,
data
})
return
await
request
.
put
({
url
:
`/member/user/update-point`
,
data
})
}
}
// 修改会员用户余额
export
const
updateUserBalance
=
async
(
data
:
any
)
=>
{
return
await
request
.
put
({
url
:
`/member/user/update-balance`
,
data
})
}
src/api/pay/wallet/balance/index.ts
View file @
f545dcc6
...
@@ -4,6 +4,7 @@ import request from '@/config/axios'
...
@@ -4,6 +4,7 @@ import request from '@/config/axios'
export
interface
PayWalletUserReqVO
{
export
interface
PayWalletUserReqVO
{
userId
:
number
userId
:
number
}
}
/** 钱包 VO */
/** 钱包 VO */
export
interface
WalletVO
{
export
interface
WalletVO
{
id
:
number
id
:
number
...
@@ -20,7 +21,12 @@ export const getWallet = async (params: PayWalletUserReqVO) => {
...
@@ -20,7 +21,12 @@ export const getWallet = async (params: PayWalletUserReqVO) => {
return
await
request
.
get
<
WalletVO
>
({
url
:
`/pay/wallet/get`
,
params
})
return
await
request
.
get
<
WalletVO
>
({
url
:
`/pay/wallet/get`
,
params
})
}
}
/
/ 查询会员钱包列表
/
** 查询会员钱包列表 */
export
const
getWalletPage
=
async
(
params
)
=>
{
export
const
getWalletPage
=
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
`/pay/wallet/page`
,
params
})
return
await
request
.
get
({
url
:
`/pay/wallet/page`
,
params
})
}
}
/** 修改会员钱包余额 */
export
const
updateWalletBalance
=
async
(
data
:
any
)
=>
{
return
await
request
.
put
({
url
:
`/pay/wallet/update-balance`
,
data
})
}
src/components/DictTag/src/DictTag.vue
View file @
f545dcc6
<
script
lang=
"tsx"
>
<
script
lang=
"tsx"
>
import
{
defineComponent
,
PropType
,
computed
}
from
'vue'
import
{
computed
,
defineComponent
,
PropType
}
from
'vue'
import
{
isHexColor
}
from
'@/utils/color'
import
{
isHexColor
}
from
'@/utils/color'
import
{
ElTag
}
from
'element-plus'
import
{
ElTag
}
from
'element-plus'
import
{
DictDataType
,
getDictOptions
}
from
'@/utils/dict'
import
{
DictDataType
,
getDictOptions
}
from
'@/utils/dict'
import
{
isArray
,
is
String
,
isNumber
,
isBoolean
}
from
'@/utils/is'
import
{
isArray
,
is
Boolean
,
isNumber
,
isString
}
from
'@/utils/is'
export
default
defineComponent
({
export
default
defineComponent
({
name
:
'DictTag'
,
name
:
'DictTag'
,
...
...
src/components/DiyEditor/components/mobile/FloatingActionButton/index.vue
View file @
f545dcc6
...
@@ -44,7 +44,7 @@ defineOptions({ name: 'FloatingActionButton' })
...
@@ -44,7 +44,7 @@ defineOptions({ name: 'FloatingActionButton' })
defineProps
<
{
property
:
FloatingActionButtonProperty
}
>
()
defineProps
<
{
property
:
FloatingActionButtonProperty
}
>
()
// 是否展开
// 是否展开
const
expanded
=
ref
(
tru
e
)
const
expanded
=
ref
(
fals
e
)
// 处理展开/折叠
// 处理展开/折叠
const
handleToggleFab
=
()
=>
{
const
handleToggleFab
=
()
=>
{
expanded
.
value
=
!
expanded
.
value
expanded
.
value
=
!
expanded
.
value
...
...
src/components/DiyEditor/components/mobile/ProductCard/index.vue
View file @
f545dcc6
...
@@ -67,15 +67,15 @@
...
@@ -67,15 +67,15 @@
class=
"text-16px"
class=
"text-16px"
:style=
"
{ color: property.fields.price.color }"
:style=
"
{ color: property.fields.price.color }"
>
>
¥
{{
spu
.
price
}}
¥
{{
fenToYuan
(
spu
.
price
)
}}
</span>
</span>
<!-- 市场价 -->
<!-- 市场价 -->
<span
<span
v-if=
"property.fields.marketPrice.show && spu.marketPrice"
v-if=
"property.fields.marketPrice.show && spu.marketPrice"
class=
"ml-4px text-10px line-through"
class=
"ml-4px text-10px line-through"
:style=
"
{ color: property.fields.marketPrice.color }"
:style=
"
{ color: property.fields.marketPrice.color }"
>¥
{{
spu
.
marketPrice
}}
</span
>¥
{{
fenToYuan
(
spu
.
marketPrice
)
}}
>
</span
>
</div>
</div>
<div
class=
"text-12px"
>
<div
class=
"text-12px"
>
<!-- 销量 -->
<!-- 销量 -->
...
@@ -117,6 +117,7 @@
...
@@ -117,6 +117,7 @@
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
ProductCardProperty
}
from
'./config'
import
{
ProductCardProperty
}
from
'./config'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
{
fenToYuan
}
from
'../../../../../utils'
/** 商品卡片 */
/** 商品卡片 */
defineOptions
({
name
:
'ProductCard'
})
defineOptions
({
name
:
'ProductCard'
})
...
...
src/components/DiyEditor/components/mobile/ProductList/index.vue
View file @
f545dcc6
...
@@ -54,7 +54,7 @@
...
@@ -54,7 +54,7 @@
class=
"text-12px"
class=
"text-12px"
:style=
"
{ color: property.fields.price.color }"
:style=
"
{ color: property.fields.price.color }"
>
>
¥
{{
spu
.
price
}}
¥
{{
fenToYuan
(
spu
.
price
)
}}
</span>
</span>
</div>
</div>
</div>
</div>
...
@@ -65,6 +65,7 @@
...
@@ -65,6 +65,7 @@
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
ProductListProperty
}
from
'./config'
import
{
ProductListProperty
}
from
'./config'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
{
fenToYuan
}
from
'@/utils'
/** 商品栏 */
/** 商品栏 */
defineOptions
({
name
:
'ProductList'
})
defineOptions
({
name
:
'ProductList'
})
...
...
src/components/DiyEditor/components/mobile/PromotionCombination/config.ts
View file @
f545dcc6
...
@@ -3,13 +3,21 @@ import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
...
@@ -3,13 +3,21 @@ import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 拼团属性 */
/** 拼团属性 */
export
interface
PromotionCombinationProperty
{
export
interface
PromotionCombinationProperty
{
// 布局类型:单列 | 三列
// 布局类型:单列 | 三列
layoutType
:
'oneCol
'
|
'three
Col'
layoutType
:
'oneCol
BigImg'
|
'oneColSmallImg'
|
'two
Col'
// 商品字段
// 商品字段
fields
:
{
fields
:
{
// 商品名称
// 商品名称
name
:
PromotionCombinationFieldProperty
name
:
PromotionCombinationFieldProperty
// 商品简介
introduction
:
PromotionCombinationFieldProperty
// 商品价格
// 商品价格
price
:
PromotionCombinationFieldProperty
price
:
PromotionCombinationFieldProperty
// 市场价
marketPrice
:
PromotionCombinationFieldProperty
// 商品销量
salesCount
:
PromotionCombinationFieldProperty
// 商品库存
stock
:
PromotionCombinationFieldProperty
}
}
// 角标
// 角标
badge
:
{
badge
:
{
...
@@ -18,6 +26,19 @@ export interface PromotionCombinationProperty {
...
@@ -18,6 +26,19 @@ export interface PromotionCombinationProperty {
// 角标图片
// 角标图片
imgUrl
:
string
imgUrl
:
string
}
}
// 按钮
btnBuy
:
{
// 类型:文字 | 图片
type
:
'text'
|
'img'
// 文字
text
:
string
// 文字按钮:背景渐变起始颜色
bgBeginColor
:
string
// 文字按钮:背景渐变结束颜色
bgEndColor
:
string
// 图片按钮:图片地址
imgUrl
:
string
}
// 上圆角
// 上圆角
borderRadiusTop
:
number
borderRadiusTop
:
number
// 下圆角
// 下圆角
...
@@ -25,7 +46,7 @@ export interface PromotionCombinationProperty {
...
@@ -25,7 +46,7 @@ export interface PromotionCombinationProperty {
// 间距
// 间距
space
:
number
space
:
number
// 拼团活动编号
// 拼团活动编号
activityId
:
number
activityId
s
:
number
[]
// 组件样式
// 组件样式
style
:
ComponentStyle
style
:
ComponentStyle
}
}
...
@@ -44,12 +65,23 @@ export const component = {
...
@@ -44,12 +65,23 @@ export const component = {
name
:
'拼团'
,
name
:
'拼团'
,
icon
:
'mdi:account-group'
,
icon
:
'mdi:account-group'
,
property
:
{
property
:
{
layoutType
:
'oneCol'
,
layoutType
:
'oneCol
BigImg
'
,
fields
:
{
fields
:
{
name
:
{
show
:
true
,
color
:
'#000'
},
name
:
{
show
:
true
,
color
:
'#000'
},
price
:
{
show
:
true
,
color
:
'#ff3000'
}
introduction
:
{
show
:
true
,
color
:
'#999'
},
price
:
{
show
:
true
,
color
:
'#ff3000'
},
marketPrice
:
{
show
:
true
,
color
:
'#c4c4c4'
},
salesCount
:
{
show
:
true
,
color
:
'#c4c4c4'
},
stock
:
{
show
:
false
,
color
:
'#c4c4c4'
}
},
},
badge
:
{
show
:
false
,
imgUrl
:
''
},
badge
:
{
show
:
false
,
imgUrl
:
''
},
btnBuy
:
{
type
:
'text'
,
text
:
'去拼团'
,
bgBeginColor
:
'#FF6000'
,
bgEndColor
:
'#FE832A'
,
imgUrl
:
''
},
borderRadiusTop
:
8
,
borderRadiusTop
:
8
,
borderRadiusBottom
:
8
,
borderRadiusBottom
:
8
,
space
:
8
,
space
:
8
,
...
...
src/components/DiyEditor/components/mobile/PromotionCombination/index.vue
View file @
f545dcc6
<
template
>
<
template
>
<el-scrollbar
class=
"z-1 min-h-30px"
wrap-class=
"w-full"
ref=
"containerRef"
>
<div
:class=
"`box-content min-h-30px w-full flex flex-row flex-wrap`"
ref=
"containerRef"
>
<!-- 商品网格 -->
<div
class=
"grid overflow-x-auto"
:style=
"
{
gridGap: `${property.space}px`,
gridTemplateColumns,
width: scrollbarWidth
}"
>
<!-- 商品 -->
<div
<div
class=
"relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
class=
"relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
:style=
"
{
:style=
"
{
...calculateSpace(index),
...calculateWidth(),
borderTopLeftRadius: `${property.borderRadiusTop}px`,
borderTopLeftRadius: `${property.borderRadiusTop}px`,
borderTopRightRadius: `${property.borderRadiusTop}px`,
borderTopRightRadius: `${property.borderRadiusTop}px`,
borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
...
@@ -22,114 +14,188 @@
...
@@ -22,114 +14,188 @@
:key="index"
:key="index"
>
>
<!-- 角标 -->
<!-- 角标 -->
<div
<div
v-if=
"property.badge.show"
class=
"absolute left-0 top-0 z-1 items-center justify-center"
>
v-if=
"property.badge.show"
class=
"absolute left-0 top-0 z-1 items-center justify-center"
>
<el-image
fit=
"cover"
:src=
"property.badge.imgUrl"
class=
"h-26px w-38px"
/>
<el-image
fit=
"cover"
:src=
"property.badge.imgUrl"
class=
"h-26px w-38px"
/>
</div>
</div>
<!-- 商品封面图 -->
<!-- 商品封面图 -->
<el-image
fit=
"cover"
:src=
"spu.picUrl"
:style=
"
{ width: imageSize, height: imageSize }" />
<div
<div
:class=
"[
:class=
"[
'flex flex-col gap-8px p-8px box-border',
'h-140px',
{
'w-full': property.layoutType !== 'oneColSmallImg',
'w-140px': property.layoutType === 'oneColSmallImg'
}
]"
>
<el-image
fit=
"cover"
class=
"h-full w-full"
:src=
"spu.picUrl"
/>
</div>
<div
:class=
"[
' flex flex-col gap-8px p-8px box-border',
{
{
'w-[calc(100%-64px)]': columns === 2
,
'w-full': property.layoutType !== 'oneColSmallImg'
,
'w-full': columns === 3
'w-[calc(100%-140px-16px)]': property.layoutType === 'oneColSmallImg'
}
}
]"
]"
>
>
<!-- 商品名称 -->
<!-- 商品名称 -->
<div
<div
v-if=
"property.fields.name.show"
v-if=
"property.fields.name.show"
class=
"truncate text-12px"
:class=
"[
'text-14px ',
{
truncate: property.layoutType !== 'oneColSmallImg',
'overflow-ellipsis line-clamp-2': property.layoutType === 'oneColSmallImg'
}
]"
:style="{ color: property.fields.name.color }"
:style="{ color: property.fields.name.color }"
>
>
{{
spu
.
name
}}
{{
spu
.
name
}}
</div>
</div>
<!-- 商品简介 -->
<div
v-if=
"property.fields.introduction.show"
class=
"truncate text-12px"
:style=
"
{ color: property.fields.introduction.color }"
>
{{
spu
.
introduction
}}
</div>
<div>
<div>
<!-- 商品
价格 -->
<!--
价格 -->
<span
<span
v-if=
"property.fields.price.show"
v-if=
"property.fields.price.show"
class=
"text-12
px"
class=
"text-16
px"
:style=
"
{ color: property.fields.price.color }"
:style=
"
{ color: property.fields.price.color }"
>
>
¥
{{
fenToYuan
(
spu
.
combinationPrice
||
spu
.
price
||
0
)
}}
¥
{{
fenToYuan
(
spu
.
price
||
Infinity
)
}}
</span>
<!-- 市场价 -->
<span
v-if=
"property.fields.marketPrice.show && spu.marketPrice"
class=
"ml-4px text-10px line-through"
:style=
"
{ color: property.fields.marketPrice.color }"
>¥
{{
fenToYuan
(
spu
.
marketPrice
)
}}
</span
>
</div>
<div
class=
"text-12px"
>
<!-- 销量 -->
<span
v-if=
"property.fields.salesCount.show"
:style=
"
{ color: property.fields.salesCount.color }"
>
已售
{{
(
spu
.
salesCount
||
0
)
+
(
spu
.
virtualSalesCount
||
0
)
}}
件
</span>
<!-- 库存 -->
<span
v-if=
"property.fields.stock.show"
:style=
"
{ color: property.fields.stock.color }">
库存
{{
spu
.
stock
||
0
}}
</span>
</span>
</div>
</div>
</div>
</div>
<!-- 购买按钮 -->
<div
class=
"absolute bottom-8px right-8px"
>
<!-- 文字按钮 -->
<span
v-if=
"property.btnBuy.type === 'text'"
class=
"rounded-full p-x-12px p-y-4px text-12px text-white"
:style=
"
{
background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`
}"
>
{{
property
.
btnBuy
.
text
}}
</span>
<!-- 图片按钮 -->
<el-image
v-else
class=
"h-28px w-28px rounded-full"
fit=
"cover"
:src=
"property.btnBuy.imgUrl"
/>
</div>
</div>
</div>
</div>
</div>
</el-scrollbar>
</
template
>
</
template
>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
PromotionCombinationProperty
}
from
'./config'
import
{
PromotionCombinationProperty
}
from
'./config'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
*
as
CombinationActivityApi
from
'@/api/mall/promotion/combination/combinationActivity'
import
*
as
CombinationActivityApi
from
'@/api/mall/promotion/combination/combinationActivity'
import
{
Spu
}
from
'@/api/mall/product/spu'
import
{
CombinationProductVO
}
from
'@/api/mall/promotion/combination/combinationActivity'
import
{
fenToYuan
}
from
'@/utils'
import
{
fenToYuan
}
from
'@/utils'
/** 拼团 */
/** 拼团
卡片
*/
defineOptions
({
name
:
'PromotionCombination'
})
defineOptions
({
name
:
'PromotionCombination'
})
// 定义属性
// 定义属性
const
props
=
defineProps
<
{
property
:
PromotionCombinationProperty
}
>
()
const
props
=
defineProps
<
{
property
:
PromotionCombinationProperty
}
>
()
// 商品列表
// 商品列表
const
spuList
=
ref
<
ProductSpuApi
.
Spu
[]
>
([])
const
spuList
=
ref
<
ProductSpuApi
.
Spu
[]
>
([])
const
spuIdList
=
ref
<
number
[]
>
([])
const
combinationActivityList
=
ref
<
CombinationActivityApi
.
CombinationActivityVO
[]
>
([])
watch
(
watch
(
()
=>
props
.
property
.
activityId
,
()
=>
props
.
property
.
activityId
s
,
async
()
=>
{
async
()
=>
{
if
(
!
props
.
property
.
activityId
)
return
try
{
const
activity
=
await
CombinationActivityApi
.
getCombinationActivity
(
props
.
property
.
activityId
)
// 新添加的拼团组件,是没有活动ID的
if
(
!
activity
?.
spuId
)
return
const
activityIds
=
props
.
property
.
activityIds
spuList
.
value
=
[
await
ProductSpuApi
.
getSpu
(
activity
.
spuId
)]
// 检查活动ID的有效性
// 循环活动信息,赋值拼团价格
if
(
Array
.
isArray
(
activityIds
)
&&
activityIds
.
length
>
0
)
{
activity
.
products
.
forEach
((
product
:
CombinationProductVO
)
=>
{
// 获取拼团活动详情列表
spuList
.
value
.
forEach
((
spu
:
Spu
)
=>
{
combinationActivityList
.
value
=
// 商品原售价和拼团价,哪个便宜就赋值哪个
await
CombinationActivityApi
.
getCombinationActivityListByIds
(
activityIds
)
spu
.
combinationPrice
=
Math
.
min
(
spu
.
combinationPrice
||
Infinity
,
product
.
combinationPrice
)
// 设置 SPU 的最低价格
})
// 获取拼团活动的 SPU 详情列表
spuList
.
value
=
[]
spuIdList
.
value
=
combinationActivityList
.
value
.
map
((
activity
)
=>
activity
.
spuId
)
.
filter
((
spuId
):
spuId
is
number
=>
typeof
spuId
===
'number'
)
if
(
spuIdList
.
value
.
length
>
0
)
{
spuList
.
value
=
await
ProductSpuApi
.
getSpuDetailList
(
spuIdList
.
value
)
}
// 更新 SPU 的最低价格
combinationActivityList
.
value
.
forEach
((
activity
)
=>
{
// 匹配spuId
const
spu
=
spuList
.
value
.
find
((
spu
)
=>
spu
.
id
===
activity
.
spuId
)
if
(
spu
)
{
// 赋值活动价格,哪个最便宜就赋值哪个
spu
.
price
=
Math
.
min
(
activity
.
combinationPrice
||
Infinity
,
spu
.
price
||
Infinity
)
}
})
})
}
}
catch
(
error
)
{
console
.
error
(
'获取拼团活动细节或 SPU 细节时出错:'
,
error
)
}
},
},
{
{
immediate
:
true
,
immediate
:
true
,
deep
:
true
deep
:
true
}
}
)
)
// 手机宽度
const
phoneWidth
=
ref
(
375
)
/**
* 计算商品的间距
* @param index 商品索引
*/
const
calculateSpace
=
(
index
:
number
)
=>
{
// 商品的列数
const
columns
=
props
.
property
.
layoutType
===
'twoCol'
?
2
:
1
// 第一列没有左边距
const
marginLeft
=
index
%
columns
===
0
?
'0'
:
props
.
property
.
space
+
'px'
// 第一行没有上边距
const
marginTop
=
index
<
columns
?
'0'
:
props
.
property
.
space
+
'px'
return
{
marginLeft
,
marginTop
}
}
// 容器
// 容器
const
containerRef
=
ref
()
const
containerRef
=
ref
()
// 商品的列数
// 计算商品的宽度
const
columns
=
ref
(
2
)
const
calculateWidth
=
()
=>
{
// 滚动条宽度
let
width
=
'100%'
const
scrollbarWidth
=
ref
(
'100%'
)
// 双列时每列的宽度为:(总宽度 - 间距)/ 2
// 商品图大小
if
(
props
.
property
.
layoutType
===
'twoCol'
)
{
const
imageSize
=
ref
(
'0'
)
width
=
`
${(
containerRef
.
value
.
offsetWidth
-
props
.
property
.
space
)
/
2
}
px`
// 商品网络列数
}
const
gridTemplateColumns
=
ref
(
''
)
return
{
width
}
// 计算布局参数
}
watch
(
()
=>
[
props
.
property
,
phoneWidth
,
spuList
.
value
.
length
],
()
=>
{
// 计算列数
columns
.
value
=
props
.
property
.
layoutType
===
'oneCol'
?
1
:
3
// 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
const
productWidth
=
(
phoneWidth
.
value
-
props
.
property
.
space
*
(
columns
.
value
-
1
))
/
columns
.
value
// 商品图布局:2列时,左右布局 3列时,上下布局
imageSize
.
value
=
columns
.
value
===
2
?
'64px'
:
`
${
productWidth
}
px`
// 指定列数
gridTemplateColumns
.
value
=
`repeat(
${
columns
.
value
}
, auto)`
// 不滚动
scrollbarWidth
.
value
=
'100%'
},
{
immediate
:
true
,
deep
:
true
}
)
onMounted
(()
=>
{
// 提取手机宽度
phoneWidth
.
value
=
containerRef
.
value
?.
wrapRef
?.
offsetWidth
||
375
})
</
script
>
</
script
>
<
style
scoped
lang=
"scss"
></
style
>
<
style
scoped
lang=
"scss"
></
style
>
src/components/DiyEditor/components/mobile/PromotionCombination/property.vue
View file @
f545dcc6
...
@@ -2,30 +2,31 @@
...
@@ -2,30 +2,31 @@
<ComponentContainerProperty
v-model=
"formData.style"
>
<ComponentContainerProperty
v-model=
"formData.style"
>
<el-form
label-width=
"80px"
:model=
"formData"
>
<el-form
label-width=
"80px"
:model=
"formData"
>
<el-card
header=
"拼团活动"
class=
"property-group"
shadow=
"never"
>
<el-card
header=
"拼团活动"
class=
"property-group"
shadow=
"never"
>
<el-form-item
label=
"拼团活动"
prop=
"activityId"
>
<CombinationShowcase
v-model=
"formData.activityIds"
/>
<el-select
v-model=
"formData.activityId"
>
<el-option
v-for=
"activity in activityList"
:key=
"activity.id"
:label=
"activity.name"
:value=
"activity.id"
/>
</el-select>
</el-form-item>
</el-card>
</el-card>
<el-card
header=
"商品样式"
class=
"property-group"
shadow=
"never"
>
<el-card
header=
"商品样式"
class=
"property-group"
shadow=
"never"
>
<el-form-item
label=
"布局"
prop=
"type"
>
<el-form-item
label=
"布局"
prop=
"type"
>
<el-radio-group
v-model=
"formData.layoutType"
>
<el-radio-group
v-model=
"formData.layoutType"
>
<el-tooltip
class=
"item"
content=
"单列"
placement=
"bottom"
>
<el-tooltip
class=
"item"
content=
"单列
大图
"
placement=
"bottom"
>
<el-radio-button
value=
"oneCol"
>
<el-radio-button
value=
"oneCol
BigImg
"
>
<Icon
icon=
"fluent:text-column-one-24-filled"
/>
<Icon
icon=
"fluent:text-column-one-24-filled"
/>
</el-radio-button>
</el-radio-button>
</el-tooltip>
</el-tooltip>
<el-tooltip
class=
"item"
content=
"三列"
placement=
"bottom"
>
<el-tooltip
class=
"item"
content=
"单列小图"
placement=
"bottom"
>
<el-radio-button
value=
"oneColSmallImg"
>
<Icon
icon=
"fluent:text-column-two-left-24-filled"
/>
</el-radio-button>
</el-tooltip>
<el-tooltip
class=
"item"
content=
"双列"
placement=
"bottom"
>
<el-radio-button
value=
"twoCol"
>
<Icon
icon=
"fluent:text-column-two-24-filled"
/>
</el-radio-button>
</el-tooltip>
<!--
<el-tooltip
class=
"item"
content=
"三列"
placement=
"bottom"
>
<el-radio-button
value=
"threeCol"
>
<el-radio-button
value=
"threeCol"
>
<Icon
icon=
"fluent:text-column-three-24-filled"
/>
<Icon
icon=
"fluent:text-column-three-24-filled"
/>
</el-radio-button>
</el-radio-button>
</el-tooltip>
</el-tooltip>
-->
</el-radio-group>
</el-radio-group>
</el-form-item>
</el-form-item>
<el-form-item
label=
"商品名称"
prop=
"fields.name.show"
>
<el-form-item
label=
"商品名称"
prop=
"fields.name.show"
>
...
@@ -34,12 +35,36 @@
...
@@ -34,12 +35,36 @@
<el-checkbox
v-model=
"formData.fields.name.show"
/>
<el-checkbox
v-model=
"formData.fields.name.show"
/>
</div>
</div>
</el-form-item>
</el-form-item>
<el-form-item
label=
"商品简介"
prop=
"fields.introduction.show"
>
<div
class=
"flex gap-8px"
>
<ColorInput
v-model=
"formData.fields.introduction.color"
/>
<el-checkbox
v-model=
"formData.fields.introduction.show"
/>
</div>
</el-form-item>
<el-form-item
label=
"商品价格"
prop=
"fields.price.show"
>
<el-form-item
label=
"商品价格"
prop=
"fields.price.show"
>
<div
class=
"flex gap-8px"
>
<div
class=
"flex gap-8px"
>
<ColorInput
v-model=
"formData.fields.price.color"
/>
<ColorInput
v-model=
"formData.fields.price.color"
/>
<el-checkbox
v-model=
"formData.fields.price.show"
/>
<el-checkbox
v-model=
"formData.fields.price.show"
/>
</div>
</div>
</el-form-item>
</el-form-item>
<el-form-item
label=
"市场价"
prop=
"fields.marketPrice.show"
>
<div
class=
"flex gap-8px"
>
<ColorInput
v-model=
"formData.fields.marketPrice.color"
/>
<el-checkbox
v-model=
"formData.fields.marketPrice.show"
/>
</div>
</el-form-item>
<el-form-item
label=
"商品销量"
prop=
"fields.salesCount.show"
>
<div
class=
"flex gap-8px"
>
<ColorInput
v-model=
"formData.fields.salesCount.color"
/>
<el-checkbox
v-model=
"formData.fields.salesCount.show"
/>
</div>
</el-form-item>
<el-form-item
label=
"商品库存"
prop=
"fields.stock.show"
>
<div
class=
"flex gap-8px"
>
<ColorInput
v-model=
"formData.fields.stock.color"
/>
<el-checkbox
v-model=
"formData.fields.stock.show"
/>
</div>
</el-form-item>
</el-card>
</el-card>
<el-card
header=
"角标"
class=
"property-group"
shadow=
"never"
>
<el-card
header=
"角标"
class=
"property-group"
shadow=
"never"
>
<el-form-item
label=
"角标"
prop=
"badge.show"
>
<el-form-item
label=
"角标"
prop=
"badge.show"
>
...
@@ -47,9 +72,35 @@
...
@@ -47,9 +72,35 @@
</el-form-item>
</el-form-item>
<el-form-item
label=
"角标"
prop=
"badge.imgUrl"
v-if=
"formData.badge.show"
>
<el-form-item
label=
"角标"
prop=
"badge.imgUrl"
v-if=
"formData.badge.show"
>
<UploadImg
v-model=
"formData.badge.imgUrl"
height=
"44px"
width=
"72px"
>
<UploadImg
v-model=
"formData.badge.imgUrl"
height=
"44px"
width=
"72px"
>
<template
#
tip
>
建议尺寸:36 * 22
</
template
>
<template
#
tip
>
建议尺寸:36 * 22
</
template
>
</UploadImg>
</el-form-item>
</el-card>
<el-card
header=
"按钮"
class=
"property-group"
shadow=
"never"
>
<el-form-item
label=
"按钮类型"
prop=
"btnBuy.type"
>
<el-radio-group
v-model=
"formData.btnBuy.type"
>
<el-radio-button
value=
"text"
>
文字
</el-radio-button>
<el-radio-button
value=
"img"
>
图片
</el-radio-button>
</el-radio-group>
</el-form-item>
<
template
v-if=
"formData.btnBuy.type === 'text'"
>
<el-form-item
label=
"按钮文字"
prop=
"btnBuy.text"
>
<el-input
v-model=
"formData.btnBuy.text"
/>
</el-form-item>
<el-form-item
label=
"左侧背景"
prop=
"btnBuy.bgBeginColor"
>
<ColorInput
v-model=
"formData.btnBuy.bgBeginColor"
/>
</el-form-item>
<el-form-item
label=
"右侧背景"
prop=
"btnBuy.bgEndColor"
>
<ColorInput
v-model=
"formData.btnBuy.bgEndColor"
/>
</el-form-item>
</
template
>
<
template
v-else
>
<el-form-item
label=
"图片"
prop=
"btnBuy.imgUrl"
>
<UploadImg
v-model=
"formData.btnBuy.imgUrl"
height=
"56px"
width=
"56px"
>
<template
#
tip
>
建议尺寸:56 * 56
</
template
>
</UploadImg>
</UploadImg>
</el-form-item>
</el-form-item>
</template>
</el-card>
</el-card>
<el-card
header=
"商品样式"
class=
"property-group"
shadow=
"never"
>
<el-card
header=
"商品样式"
class=
"property-group"
shadow=
"never"
>
<el-form-item
label=
"上圆角"
prop=
"borderRadiusTop"
>
<el-form-item
label=
"上圆角"
prop=
"borderRadiusTop"
>
...
@@ -92,6 +143,7 @@ import { PromotionCombinationProperty } from './config'
...
@@ -92,6 +143,7 @@ import { PromotionCombinationProperty } from './config'
import
{
usePropertyForm
}
from
'@/components/DiyEditor/util'
import
{
usePropertyForm
}
from
'@/components/DiyEditor/util'
import
*
as
CombinationActivityApi
from
'@/api/mall/promotion/combination/combinationActivity'
import
*
as
CombinationActivityApi
from
'@/api/mall/promotion/combination/combinationActivity'
import
{
CommonStatusEnum
}
from
'@/utils/constants'
import
{
CommonStatusEnum
}
from
'@/utils/constants'
import
CombinationShowcase
from
'@/views/mall/promotion/combination/components/CombinationShowcase.vue'
// 拼团属性面板
// 拼团属性面板
defineOptions
({
name
:
'PromotionCombinationProperty'
})
defineOptions
({
name
:
'PromotionCombinationProperty'
})
...
@@ -100,7 +152,7 @@ const props = defineProps<{ modelValue: PromotionCombinationProperty }>()
...
@@ -100,7 +152,7 @@ const props = defineProps<{ modelValue: PromotionCombinationProperty }>()
const
emit
=
defineEmits
([
'update:modelValue'
])
const
emit
=
defineEmits
([
'update:modelValue'
])
const
{
formData
}
=
usePropertyForm
(
props
.
modelValue
,
emit
)
const
{
formData
}
=
usePropertyForm
(
props
.
modelValue
,
emit
)
// 活动列表
// 活动列表
const
activityList
=
ref
<
CombinationActivityApi
.
CombinationActivityVO
>
([])
const
activityList
=
ref
<
CombinationActivityApi
.
CombinationActivityVO
[]
>
([])
onMounted
(
async
()
=>
{
onMounted
(
async
()
=>
{
const
{
list
}
=
await
CombinationActivityApi
.
getCombinationActivityPage
({
const
{
list
}
=
await
CombinationActivityApi
.
getCombinationActivityPage
({
status
:
CommonStatusEnum
.
ENABLE
status
:
CommonStatusEnum
.
ENABLE
...
...
src/components/DiyEditor/components/mobile/PromotionSeckill/config.ts
View file @
f545dcc6
...
@@ -3,13 +3,21 @@ import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
...
@@ -3,13 +3,21 @@ import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
/** 秒杀属性 */
/** 秒杀属性 */
export
interface
PromotionSeckillProperty
{
export
interface
PromotionSeckillProperty
{
// 布局类型:单列 | 三列
// 布局类型:单列 | 三列
layoutType
:
'oneCol
'
|
'three
Col'
layoutType
:
'oneCol
BigImg'
|
'oneColSmallImg'
|
'two
Col'
// 商品字段
// 商品字段
fields
:
{
fields
:
{
// 商品名称
// 商品名称
name
:
PromotionSeckillFieldProperty
name
:
PromotionSeckillFieldProperty
// 商品简介
introduction
:
PromotionSeckillFieldProperty
// 商品价格
// 商品价格
price
:
PromotionSeckillFieldProperty
price
:
PromotionSeckillFieldProperty
// 市场价
marketPrice
:
PromotionSeckillFieldProperty
// 商品销量
salesCount
:
PromotionSeckillFieldProperty
// 商品库存
stock
:
PromotionSeckillFieldProperty
}
}
// 角标
// 角标
badge
:
{
badge
:
{
...
@@ -18,6 +26,19 @@ export interface PromotionSeckillProperty {
...
@@ -18,6 +26,19 @@ export interface PromotionSeckillProperty {
// 角标图片
// 角标图片
imgUrl
:
string
imgUrl
:
string
}
}
// 按钮
btnBuy
:
{
// 类型:文字 | 图片
type
:
'text'
|
'img'
// 文字
text
:
string
// 文字按钮:背景渐变起始颜色
bgBeginColor
:
string
// 文字按钮:背景渐变结束颜色
bgEndColor
:
string
// 图片按钮:图片地址
imgUrl
:
string
}
// 上圆角
// 上圆角
borderRadiusTop
:
number
borderRadiusTop
:
number
// 下圆角
// 下圆角
...
@@ -25,10 +46,11 @@ export interface PromotionSeckillProperty {
...
@@ -25,10 +46,11 @@ export interface PromotionSeckillProperty {
// 间距
// 间距
space
:
number
space
:
number
// 秒杀活动编号
// 秒杀活动编号
activityId
:
number
activityId
s
:
number
[]
// 组件样式
// 组件样式
style
:
ComponentStyle
style
:
ComponentStyle
}
}
// 商品字段
// 商品字段
export
interface
PromotionSeckillFieldProperty
{
export
interface
PromotionSeckillFieldProperty
{
// 是否显示
// 是否显示
...
@@ -43,13 +65,23 @@ export const component = {
...
@@ -43,13 +65,23 @@ export const component = {
name
:
'秒杀'
,
name
:
'秒杀'
,
icon
:
'mdi:calendar-time'
,
icon
:
'mdi:calendar-time'
,
property
:
{
property
:
{
activityId
:
undefined
,
layoutType
:
'oneColBigImg'
,
layoutType
:
'oneCol'
,
fields
:
{
fields
:
{
name
:
{
show
:
true
,
color
:
'#000'
},
name
:
{
show
:
true
,
color
:
'#000'
},
price
:
{
show
:
true
,
color
:
'#ff3000'
}
introduction
:
{
show
:
true
,
color
:
'#999'
},
price
:
{
show
:
true
,
color
:
'#ff3000'
},
marketPrice
:
{
show
:
true
,
color
:
'#c4c4c4'
},
salesCount
:
{
show
:
true
,
color
:
'#c4c4c4'
},
stock
:
{
show
:
false
,
color
:
'#c4c4c4'
}
},
},
badge
:
{
show
:
false
,
imgUrl
:
''
},
badge
:
{
show
:
false
,
imgUrl
:
''
},
btnBuy
:
{
type
:
'text'
,
text
:
'立即秒杀'
,
bgBeginColor
:
'#FF6000'
,
bgEndColor
:
'#FE832A'
,
imgUrl
:
''
},
borderRadiusTop
:
8
,
borderRadiusTop
:
8
,
borderRadiusBottom
:
8
,
borderRadiusBottom
:
8
,
space
:
8
,
space
:
8
,
...
...
src/components/DiyEditor/components/mobile/PromotionSeckill/index.vue
View file @
f545dcc6
<
template
>
<
template
>
<el-scrollbar
class=
"z-1 min-h-30px"
wrap-class=
"w-full"
ref=
"containerRef"
>
<div
:class=
"`box-content min-h-30px w-full flex flex-row flex-wrap`"
ref=
"containerRef"
>
<!-- 商品网格 -->
<div
class=
"grid overflow-x-auto"
:style=
"
{
gridGap: `${property.space}px`,
gridTemplateColumns,
width: scrollbarWidth
}"
>
<!-- 商品 -->
<div
<div
class=
"relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
class=
"relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
:style=
"
{
:style=
"
{
...calculateSpace(index),
...calculateWidth(),
borderTopLeftRadius: `${property.borderRadiusTop}px`,
borderTopLeftRadius: `${property.borderRadiusTop}px`,
borderTopRightRadius: `${property.borderRadiusTop}px`,
borderTopRightRadius: `${property.borderRadiusTop}px`,
borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
...
@@ -22,114 +14,188 @@
...
@@ -22,114 +14,188 @@
:key="index"
:key="index"
>
>
<!-- 角标 -->
<!-- 角标 -->
<div
<div
v-if=
"property.badge.show"
class=
"absolute left-0 top-0 z-1 items-center justify-center"
>
v-if=
"property.badge.show"
class=
"absolute left-0 top-0 z-1 items-center justify-center"
>
<el-image
fit=
"cover"
:src=
"property.badge.imgUrl"
class=
"h-26px w-38px"
/>
<el-image
fit=
"cover"
:src=
"property.badge.imgUrl"
class=
"h-26px w-38px"
/>
</div>
</div>
<!-- 商品封面图 -->
<!-- 商品封面图 -->
<el-image
fit=
"cover"
:src=
"spu.picUrl"
:style=
"
{ width: imageSize, height: imageSize }" />
<div
<div
:class=
"[
:class=
"[
'flex flex-col gap-8px p-8px box-border',
'h-140px',
{
'w-full': property.layoutType !== 'oneColSmallImg',
'w-140px': property.layoutType === 'oneColSmallImg'
}
]"
>
<el-image
fit=
"cover"
class=
"h-full w-full"
:src=
"spu.picUrl"
/>
</div>
<div
:class=
"[
' flex flex-col gap-8px p-8px box-border',
{
{
'w-[calc(100%-64px)]': columns === 2
,
'w-full': property.layoutType !== 'oneColSmallImg'
,
'w-full': columns === 3
'w-[calc(100%-140px-16px)]': property.layoutType === 'oneColSmallImg'
}
}
]"
]"
>
>
<!-- 商品名称 -->
<!-- 商品名称 -->
<div
<div
v-if=
"property.fields.name.show"
v-if=
"property.fields.name.show"
class=
"truncate text-12px"
:class=
"[
'text-14px ',
{
truncate: property.layoutType !== 'oneColSmallImg',
'overflow-ellipsis line-clamp-2': property.layoutType === 'oneColSmallImg'
}
]"
:style="{ color: property.fields.name.color }"
:style="{ color: property.fields.name.color }"
>
>
{{
spu
.
name
}}
{{
spu
.
name
}}
</div>
</div>
<!-- 商品简介 -->
<div
v-if=
"property.fields.introduction.show"
class=
"truncate text-12px"
:style=
"
{ color: property.fields.introduction.color }"
>
{{
spu
.
introduction
}}
</div>
<div>
<div>
<!-- 商品
价格 -->
<!--
价格 -->
<span
<span
v-if=
"property.fields.price.show"
v-if=
"property.fields.price.show"
class=
"text-12
px"
class=
"text-16
px"
:style=
"
{ color: property.fields.price.color }"
:style=
"
{ color: property.fields.price.color }"
>
>
¥
{{
fenToYuan
(
spu
.
seckillPrice
||
spu
.
price
||
0
)
}}
¥
{{
fenToYuan
(
spu
.
price
||
Infinity
)
}}
</span>
<!-- 市场价 -->
<span
v-if=
"property.fields.marketPrice.show && spu.marketPrice"
class=
"ml-4px text-10px line-through"
:style=
"
{ color: property.fields.marketPrice.color }"
>¥
{{
fenToYuan
(
spu
.
marketPrice
)
}}
</span
>
</div>
<div
class=
"text-12px"
>
<!-- 销量 -->
<span
v-if=
"property.fields.salesCount.show"
:style=
"
{ color: property.fields.salesCount.color }"
>
已售
{{
(
spu
.
salesCount
||
0
)
+
(
spu
.
virtualSalesCount
||
0
)
}}
件
</span>
<!-- 库存 -->
<span
v-if=
"property.fields.stock.show"
:style=
"
{ color: property.fields.stock.color }">
库存
{{
spu
.
stock
||
0
}}
</span>
</span>
</div>
</div>
</div>
</div>
<!-- 购买按钮 -->
<div
class=
"absolute bottom-8px right-8px"
>
<!-- 文字按钮 -->
<span
v-if=
"property.btnBuy.type === 'text'"
class=
"rounded-full p-x-12px p-y-4px text-12px text-white"
:style=
"
{
background: `linear-gradient(to right, ${property.btnBuy.bgBeginColor}, ${property.btnBuy.bgEndColor}`
}"
>
{{
property
.
btnBuy
.
text
}}
</span>
<!-- 图片按钮 -->
<el-image
v-else
class=
"h-28px w-28px rounded-full"
fit=
"cover"
:src=
"property.btnBuy.imgUrl"
/>
</div>
</div>
</div>
</div>
</div>
</el-scrollbar>
</
template
>
</
template
>
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
PromotionSeckillProperty
}
from
'./config'
import
{
PromotionSeckillProperty
}
from
'./config'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
*
as
SeckillActivityApi
from
'@/api/mall/promotion/seckill/seckillActivity'
import
*
as
SeckillActivityApi
from
'@/api/mall/promotion/seckill/seckillActivity'
import
{
Spu
}
from
'@/api/mall/product/spu'
import
{
SeckillProductVO
}
from
'@/api/mall/promotion/seckill/seckillActivity'
import
{
fenToYuan
}
from
'@/utils'
import
{
fenToYuan
}
from
'@/utils'
/** 秒杀 */
/** 秒杀
卡片
*/
defineOptions
({
name
:
'PromotionSeckill'
})
defineOptions
({
name
:
'PromotionSeckill'
})
// 定义属性
// 定义属性
const
props
=
defineProps
<
{
property
:
PromotionSeckillProperty
}
>
()
const
props
=
defineProps
<
{
property
:
PromotionSeckillProperty
}
>
()
// 商品列表
// 商品列表
const
spuList
=
ref
<
ProductSpuApi
.
Spu
[]
>
([])
const
spuList
=
ref
<
ProductSpuApi
.
Spu
[]
>
([])
const
spuIdList
=
ref
<
number
[]
>
([])
const
seckillActivityList
=
ref
<
SeckillActivityApi
.
SeckillActivityVO
[]
>
([])
watch
(
watch
(
()
=>
props
.
property
.
activityId
,
()
=>
props
.
property
.
activityId
s
,
async
()
=>
{
async
()
=>
{
if
(
!
props
.
property
.
activityId
)
return
try
{
const
activity
=
await
SeckillActivityApi
.
getSeckillActivity
(
props
.
property
.
activityId
)
// 新添加的秒杀组件,是没有活动ID的
if
(
!
activity
?.
spuId
)
return
const
activityIds
=
props
.
property
.
activityIds
spuList
.
value
=
[
await
ProductSpuApi
.
getSpu
(
activity
.
spuId
)]
// 检查活动ID的有效性
spuList
.
value
=
[
await
ProductSpuApi
.
getSpu
(
activity
.
spuId
)]
if
(
Array
.
isArray
(
activityIds
)
&&
activityIds
.
length
>
0
)
{
// 循环活动信息,赋值秒杀最低价格
// 获取秒杀活动详情列表
activity
.
products
.
forEach
((
product
:
SeckillProductVO
)
=>
{
seckillActivityList
.
value
=
spuList
.
value
.
forEach
((
spu
:
Spu
)
=>
{
await
SeckillActivityApi
.
getSeckillActivityListByIds
(
activityIds
)
spu
.
seckillPrice
=
Math
.
min
(
spu
.
seckillPrice
||
Infinity
,
product
.
seckillPrice
)
// 设置 SPU 的最低价格
})
// 获取秒杀活动的 SPU 详情列表
spuList
.
value
=
[]
spuIdList
.
value
=
seckillActivityList
.
value
.
map
((
activity
)
=>
activity
.
spuId
)
.
filter
((
spuId
):
spuId
is
number
=>
typeof
spuId
===
'number'
)
if
(
spuIdList
.
value
.
length
>
0
)
{
spuList
.
value
=
await
ProductSpuApi
.
getSpuDetailList
(
spuIdList
.
value
)
}
// 更新 SPU 的最低价格
seckillActivityList
.
value
.
forEach
((
activity
)
=>
{
// 匹配spuId
const
spu
=
spuList
.
value
.
find
((
spu
)
=>
spu
.
id
===
activity
.
spuId
)
if
(
spu
)
{
// 赋值活动价格,哪个最便宜就赋值哪个
spu
.
price
=
Math
.
min
(
activity
.
seckillPrice
||
Infinity
,
spu
.
price
||
Infinity
)
}
})
})
}
}
catch
(
error
)
{
console
.
error
(
'获取秒杀活动细节或 SPU 细节时出错:'
,
error
)
}
},
},
{
{
immediate
:
true
,
immediate
:
true
,
deep
:
true
deep
:
true
}
}
)
)
// 手机宽度
const
phoneWidth
=
ref
(
375
)
/**
* 计算商品的间距
* @param index 商品索引
*/
const
calculateSpace
=
(
index
:
number
)
=>
{
// 商品的列数
const
columns
=
props
.
property
.
layoutType
===
'twoCol'
?
2
:
1
// 第一列没有左边距
const
marginLeft
=
index
%
columns
===
0
?
'0'
:
props
.
property
.
space
+
'px'
// 第一行没有上边距
const
marginTop
=
index
<
columns
?
'0'
:
props
.
property
.
space
+
'px'
return
{
marginLeft
,
marginTop
}
}
// 容器
// 容器
const
containerRef
=
ref
()
const
containerRef
=
ref
()
// 商品的列数
// 计算商品的宽度
const
columns
=
ref
(
2
)
const
calculateWidth
=
()
=>
{
// 滚动条宽度
let
width
=
'100%'
const
scrollbarWidth
=
ref
(
'100%'
)
// 双列时每列的宽度为:(总宽度 - 间距)/ 2
// 商品图大小
if
(
props
.
property
.
layoutType
===
'twoCol'
)
{
const
imageSize
=
ref
(
'0'
)
width
=
`
${(
containerRef
.
value
.
offsetWidth
-
props
.
property
.
space
)
/
2
}
px`
// 商品网络列数
}
const
gridTemplateColumns
=
ref
(
''
)
return
{
width
}
// 计算布局参数
}
watch
(
()
=>
[
props
.
property
,
phoneWidth
,
spuList
.
value
.
length
],
()
=>
{
// 计算列数
columns
.
value
=
props
.
property
.
layoutType
===
'oneCol'
?
1
:
3
// 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
const
productWidth
=
(
phoneWidth
.
value
-
props
.
property
.
space
*
(
columns
.
value
-
1
))
/
columns
.
value
// 商品图布局:2列时,左右布局 3列时,上下布局
imageSize
.
value
=
columns
.
value
===
2
?
'64px'
:
`
${
productWidth
}
px`
// 指定列数
gridTemplateColumns
.
value
=
`repeat(
${
columns
.
value
}
, auto)`
// 不滚动
scrollbarWidth
.
value
=
'100%'
},
{
immediate
:
true
,
deep
:
true
}
)
onMounted
(()
=>
{
// 提取手机宽度
phoneWidth
.
value
=
containerRef
.
value
?.
wrapRef
?.
offsetWidth
||
375
})
</
script
>
</
script
>
<
style
scoped
lang=
"scss"
></
style
>
<
style
scoped
lang=
"scss"
></
style
>
src/components/DiyEditor/components/mobile/PromotionSeckill/property.vue
View file @
f545dcc6
...
@@ -2,30 +2,31 @@
...
@@ -2,30 +2,31 @@
<ComponentContainerProperty
v-model=
"formData.style"
>
<ComponentContainerProperty
v-model=
"formData.style"
>
<el-form
label-width=
"80px"
:model=
"formData"
>
<el-form
label-width=
"80px"
:model=
"formData"
>
<el-card
header=
"秒杀活动"
class=
"property-group"
shadow=
"never"
>
<el-card
header=
"秒杀活动"
class=
"property-group"
shadow=
"never"
>
<el-form-item
label=
"秒杀活动"
prop=
"activityId"
>
<SeckillShowcase
v-model=
"formData.activityIds"
/>
<el-select
v-model=
"formData.activityId"
>
<el-option
v-for=
"activity in activityList"
:key=
"activity.id"
:label=
"activity.name"
:value=
"activity.id"
/>
</el-select>
</el-form-item>
</el-card>
</el-card>
<el-card
header=
"商品样式"
class=
"property-group"
shadow=
"never"
>
<el-card
header=
"商品样式"
class=
"property-group"
shadow=
"never"
>
<el-form-item
label=
"布局"
prop=
"type"
>
<el-form-item
label=
"布局"
prop=
"type"
>
<el-radio-group
v-model=
"formData.layoutType"
>
<el-radio-group
v-model=
"formData.layoutType"
>
<el-tooltip
class=
"item"
content=
"单列"
placement=
"bottom"
>
<el-tooltip
class=
"item"
content=
"单列
大图
"
placement=
"bottom"
>
<el-radio-button
value=
"oneCol"
>
<el-radio-button
value=
"oneCol
BigImg
"
>
<Icon
icon=
"fluent:text-column-one-24-filled"
/>
<Icon
icon=
"fluent:text-column-one-24-filled"
/>
</el-radio-button>
</el-radio-button>
</el-tooltip>
</el-tooltip>
<el-tooltip
class=
"item"
content=
"三列"
placement=
"bottom"
>
<el-tooltip
class=
"item"
content=
"单列小图"
placement=
"bottom"
>
<el-radio-button
value=
"oneColSmallImg"
>
<Icon
icon=
"fluent:text-column-two-left-24-filled"
/>
</el-radio-button>
</el-tooltip>
<el-tooltip
class=
"item"
content=
"双列"
placement=
"bottom"
>
<el-radio-button
value=
"twoCol"
>
<Icon
icon=
"fluent:text-column-two-24-filled"
/>
</el-radio-button>
</el-tooltip>
<!--
<el-tooltip
class=
"item"
content=
"三列"
placement=
"bottom"
>
<el-radio-button
value=
"threeCol"
>
<el-radio-button
value=
"threeCol"
>
<Icon
icon=
"fluent:text-column-three-24-filled"
/>
<Icon
icon=
"fluent:text-column-three-24-filled"
/>
</el-radio-button>
</el-radio-button>
</el-tooltip>
</el-tooltip>
-->
</el-radio-group>
</el-radio-group>
</el-form-item>
</el-form-item>
<el-form-item
label=
"商品名称"
prop=
"fields.name.show"
>
<el-form-item
label=
"商品名称"
prop=
"fields.name.show"
>
...
@@ -34,12 +35,36 @@
...
@@ -34,12 +35,36 @@
<el-checkbox
v-model=
"formData.fields.name.show"
/>
<el-checkbox
v-model=
"formData.fields.name.show"
/>
</div>
</div>
</el-form-item>
</el-form-item>
<el-form-item
label=
"商品简介"
prop=
"fields.introduction.show"
>
<div
class=
"flex gap-8px"
>
<ColorInput
v-model=
"formData.fields.introduction.color"
/>
<el-checkbox
v-model=
"formData.fields.introduction.show"
/>
</div>
</el-form-item>
<el-form-item
label=
"商品价格"
prop=
"fields.price.show"
>
<el-form-item
label=
"商品价格"
prop=
"fields.price.show"
>
<div
class=
"flex gap-8px"
>
<div
class=
"flex gap-8px"
>
<ColorInput
v-model=
"formData.fields.price.color"
/>
<ColorInput
v-model=
"formData.fields.price.color"
/>
<el-checkbox
v-model=
"formData.fields.price.show"
/>
<el-checkbox
v-model=
"formData.fields.price.show"
/>
</div>
</div>
</el-form-item>
</el-form-item>
<el-form-item
label=
"市场价"
prop=
"fields.marketPrice.show"
>
<div
class=
"flex gap-8px"
>
<ColorInput
v-model=
"formData.fields.marketPrice.color"
/>
<el-checkbox
v-model=
"formData.fields.marketPrice.show"
/>
</div>
</el-form-item>
<el-form-item
label=
"商品销量"
prop=
"fields.salesCount.show"
>
<div
class=
"flex gap-8px"
>
<ColorInput
v-model=
"formData.fields.salesCount.color"
/>
<el-checkbox
v-model=
"formData.fields.salesCount.show"
/>
</div>
</el-form-item>
<el-form-item
label=
"商品库存"
prop=
"fields.stock.show"
>
<div
class=
"flex gap-8px"
>
<ColorInput
v-model=
"formData.fields.stock.color"
/>
<el-checkbox
v-model=
"formData.fields.stock.show"
/>
</div>
</el-form-item>
</el-card>
</el-card>
<el-card
header=
"角标"
class=
"property-group"
shadow=
"never"
>
<el-card
header=
"角标"
class=
"property-group"
shadow=
"never"
>
<el-form-item
label=
"角标"
prop=
"badge.show"
>
<el-form-item
label=
"角标"
prop=
"badge.show"
>
...
@@ -47,9 +72,35 @@
...
@@ -47,9 +72,35 @@
</el-form-item>
</el-form-item>
<el-form-item
label=
"角标"
prop=
"badge.imgUrl"
v-if=
"formData.badge.show"
>
<el-form-item
label=
"角标"
prop=
"badge.imgUrl"
v-if=
"formData.badge.show"
>
<UploadImg
v-model=
"formData.badge.imgUrl"
height=
"44px"
width=
"72px"
>
<UploadImg
v-model=
"formData.badge.imgUrl"
height=
"44px"
width=
"72px"
>
<template
#
tip
>
建议尺寸:36 * 22
</
template
>
<template
#
tip
>
建议尺寸:36 * 22
</
template
>
</UploadImg>
</el-form-item>
</el-card>
<el-card
header=
"按钮"
class=
"property-group"
shadow=
"never"
>
<el-form-item
label=
"按钮类型"
prop=
"btnBuy.type"
>
<el-radio-group
v-model=
"formData.btnBuy.type"
>
<el-radio-button
value=
"text"
>
文字
</el-radio-button>
<el-radio-button
value=
"img"
>
图片
</el-radio-button>
</el-radio-group>
</el-form-item>
<
template
v-if=
"formData.btnBuy.type === 'text'"
>
<el-form-item
label=
"按钮文字"
prop=
"btnBuy.text"
>
<el-input
v-model=
"formData.btnBuy.text"
/>
</el-form-item>
<el-form-item
label=
"左侧背景"
prop=
"btnBuy.bgBeginColor"
>
<ColorInput
v-model=
"formData.btnBuy.bgBeginColor"
/>
</el-form-item>
<el-form-item
label=
"右侧背景"
prop=
"btnBuy.bgEndColor"
>
<ColorInput
v-model=
"formData.btnBuy.bgEndColor"
/>
</el-form-item>
</
template
>
<
template
v-else
>
<el-form-item
label=
"图片"
prop=
"btnBuy.imgUrl"
>
<UploadImg
v-model=
"formData.btnBuy.imgUrl"
height=
"56px"
width=
"56px"
>
<template
#
tip
>
建议尺寸:56 * 56
</
template
>
</UploadImg>
</UploadImg>
</el-form-item>
</el-form-item>
</template>
</el-card>
</el-card>
<el-card
header=
"商品样式"
class=
"property-group"
shadow=
"never"
>
<el-card
header=
"商品样式"
class=
"property-group"
shadow=
"never"
>
<el-form-item
label=
"上圆角"
prop=
"borderRadiusTop"
>
<el-form-item
label=
"上圆角"
prop=
"borderRadiusTop"
>
...
@@ -92,6 +143,7 @@ import { PromotionSeckillProperty } from './config'
...
@@ -92,6 +143,7 @@ import { PromotionSeckillProperty } from './config'
import
{
usePropertyForm
}
from
'@/components/DiyEditor/util'
import
{
usePropertyForm
}
from
'@/components/DiyEditor/util'
import
*
as
SeckillActivityApi
from
'@/api/mall/promotion/seckill/seckillActivity'
import
*
as
SeckillActivityApi
from
'@/api/mall/promotion/seckill/seckillActivity'
import
{
CommonStatusEnum
}
from
'@/utils/constants'
import
{
CommonStatusEnum
}
from
'@/utils/constants'
import
SeckillShowcase
from
'@/views/mall/promotion/seckill/components/SeckillShowcase.vue'
// 秒杀属性面板
// 秒杀属性面板
defineOptions
({
name
:
'PromotionSeckillProperty'
})
defineOptions
({
name
:
'PromotionSeckillProperty'
})
...
@@ -100,7 +152,7 @@ const props = defineProps<{ modelValue: PromotionSeckillProperty }>()
...
@@ -100,7 +152,7 @@ const props = defineProps<{ modelValue: PromotionSeckillProperty }>()
const
emit
=
defineEmits
([
'update:modelValue'
])
const
emit
=
defineEmits
([
'update:modelValue'
])
const
{
formData
}
=
usePropertyForm
(
props
.
modelValue
,
emit
)
const
{
formData
}
=
usePropertyForm
(
props
.
modelValue
,
emit
)
// 活动列表
// 活动列表
const
activityList
=
ref
<
SeckillActivityApi
.
SeckillActivityVO
>
([])
const
activityList
=
ref
<
SeckillActivityApi
.
SeckillActivityVO
[]
>
([])
onMounted
(
async
()
=>
{
onMounted
(
async
()
=>
{
const
{
list
}
=
await
SeckillActivityApi
.
getSeckillActivityPage
({
const
{
list
}
=
await
SeckillActivityApi
.
getSeckillActivityPage
({
status
:
CommonStatusEnum
.
ENABLE
status
:
CommonStatusEnum
.
ENABLE
...
...
src/components/Icon/src/IconSelect.vue
View file @
f545dcc6
...
@@ -11,6 +11,10 @@ const props = defineProps({
...
@@ -11,6 +11,10 @@ const props = defineProps({
modelValue
:
{
modelValue
:
{
require
:
false
,
require
:
false
,
type
:
String
type
:
String
},
clearable
:
{
require
:
false
,
type
:
Boolean
}
}
})
})
const
emit
=
defineEmits
<
{
(
e
:
'update:modelValue'
,
v
:
string
)
}
>
()
const
emit
=
defineEmits
<
{
(
e
:
'update:modelValue'
,
v
:
string
)
}
>
()
...
@@ -92,6 +96,12 @@ function onCurrentChange(page) {
...
@@ -92,6 +96,12 @@ function onCurrentChange(page) {
currentPage
.
value
=
page
currentPage
.
value
=
page
}
}
function
clearIcon
()
{
icon
.
value
=
''
emit
(
'update:modelValue'
,
''
)
visible
.
value
=
false
}
watch
(
watch
(
()
=>
{
()
=>
{
return
props
.
modelValue
return
props
.
modelValue
...
@@ -115,7 +125,7 @@ watch(
...
@@ -115,7 +125,7 @@ watch(
<
template
>
<
template
>
<div
class=
"selector"
>
<div
class=
"selector"
>
<ElInput
v-model=
"inputValue"
@
click=
"visible = !visible"
>
<ElInput
v-model=
"inputValue"
@
click=
"visible = !visible"
:clearable=
"props.clearable"
@
clear=
"clearIcon"
>
<template
#
append
>
<template
#
append
>
<ElPopover
<ElPopover
:popper-options=
"
{
:popper-options=
"
{
...
...
src/components/UploadFile/src/UploadFile.vue
View file @
f545dcc6
<
template
>
<
template
>
<div
class=
"upload-file"
>
<div
v-if=
"!disabled"
class=
"upload-file"
>
<el-upload
<el-upload
ref=
"uploadRef"
ref=
"uploadRef"
v-model:file-list=
"fileList"
v-model:file-list=
"fileList"
...
@@ -20,11 +20,11 @@
...
@@ -20,11 +20,11 @@
class=
"upload-file-uploader"
class=
"upload-file-uploader"
name=
"file"
name=
"file"
>
>
<el-button
v-if=
"!disabled"
type=
"primary"
>
<el-button
type=
"primary"
>
<Icon
icon=
"ep:upload-filled"
/>
<Icon
icon=
"ep:upload-filled"
/>
选取文件
选取文件
</el-button>
</el-button>
<template
v-if=
"isShowTip
&& !disabled
"
#
tip
>
<template
v-if=
"isShowTip"
#
tip
>
<div
style=
"font-size: 8px"
>
<div
style=
"font-size: 8px"
>
大小不超过
<b
style=
"color: #f56c6c"
>
{{
fileSize
}}
MB
</b>
大小不超过
<b
style=
"color: #f56c6c"
>
{{
fileSize
}}
MB
</b>
</div>
</div>
...
@@ -32,7 +32,6 @@
...
@@ -32,7 +32,6 @@
格式为
<b
style=
"color: #f56c6c"
>
{{
fileType
.
join
(
'/'
)
}}
</b>
的文件
格式为
<b
style=
"color: #f56c6c"
>
{{
fileType
.
join
(
'/'
)
}}
</b>
的文件
</div>
</div>
</
template
>
</
template
>
<!-- TODO @puhui999:1)表单展示的时候,位置会偏掉,已发微信;2)disable 的时候,应该把【删除】按钮也隐藏掉? -->
<
template
#
file=
"row"
>
<
template
#
file=
"row"
>
<div
class=
"flex items-center"
>
<div
class=
"flex items-center"
>
<span>
{{
row
.
file
.
name
}}
</span>
<span>
{{
row
.
file
.
name
}}
</span>
...
@@ -54,6 +53,18 @@
...
@@ -54,6 +53,18 @@
</
template
>
</
template
>
</el-upload>
</el-upload>
</div>
</div>
<!-- 上传操作禁用时 -->
<div
v-if=
"disabled"
class=
"upload-file"
>
<div
v-for=
"(file, index) in fileList"
:key=
"index"
class=
"flex items-center file-list-item"
>
<span>
{{ file.name }}
</span>
<div
class=
"ml-10px"
>
<el-link
:href=
"file.url"
:underline=
"false"
download
target=
"_blank"
type=
"primary"
>
下载
</el-link>
</div>
</div>
</div>
</template>
</template>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
propTypes
}
from
'@/utils/propTypes'
...
@@ -211,4 +222,9 @@ const emitUpdateModelValue = () => {
...
@@ -211,4 +222,9 @@ const emitUpdateModelValue = () => {
:deep
(
.ele-upload-list__item-content-action
.el-link
)
{
:deep
(
.ele-upload-list__item-content-action
.el-link
)
{
margin-right
:
10px
;
margin-right
:
10px
;
}
}
.file-list-item
{
border
:
1px
dashed
var
(
--el-border-color-darker
);
border-radius
:
8px
;
}
</
style
>
</
style
>
src/config/axios/service.ts
View file @
f545dcc6
...
@@ -206,15 +206,12 @@ const refreshToken = async () => {
...
@@ -206,15 +206,12 @@ const refreshToken = async () => {
const
handleAuthorized
=
()
=>
{
const
handleAuthorized
=
()
=>
{
const
{
t
}
=
useI18n
()
const
{
t
}
=
useI18n
()
if
(
!
isRelogin
.
show
)
{
if
(
!
isRelogin
.
show
)
{
// 如果已经到重新登录页面则不进行弹窗提示
if
(
window
.
location
.
href
.
includes
(
'login?redirect='
))
{
return
}
isRelogin
.
show
=
true
isRelogin
.
show
=
true
ElMessageBox
.
confirm
(
t
(
'sys.api.timeoutMessage'
),
t
(
'common.confirmTitle'
),
{
ElMessageBox
.
confirm
(
t
(
'sys.api.timeoutMessage'
),
t
(
'common.confirmTitle'
),
{
showCancelButton
:
false
,
showCancelButton
:
false
,
closeOnClickModal
:
false
,
closeOnClickModal
:
false
,
showClose
:
false
,
showClose
:
false
,
closeOnPressEscape
:
false
,
confirmButtonText
:
t
(
'login.relogin'
),
confirmButtonText
:
t
(
'login.relogin'
),
type
:
'warning'
type
:
'warning'
}).
then
(()
=>
{
}).
then
(()
=>
{
...
...
src/layout/components/Setting/src/Setting.vue
View file @
f545dcc6
...
@@ -129,7 +129,7 @@ const copyConfig = async () => {
...
@@ -129,7 +129,7 @@ const copyConfig = async () => {
// 标签页
// 标签页
tagsViewImmerse:
${
appStore
.
getTagsViewImmerse
}
,
tagsViewImmerse:
${
appStore
.
getTagsViewImmerse
}
,
// 标签页图标
// 标签页图标
getT
agsViewIcon:
${
appStore
.
getTagsViewIcon
}
,
t
agsViewIcon:
${
appStore
.
getTagsViewIcon
}
,
// logo
// logo
logo:
${
appStore
.
getLogo
}
,
logo:
${
appStore
.
getLogo
}
,
// 菜单手风琴
// 菜单手风琴
...
...
src/layout/components/TagsView/src/TagsView.vue
View file @
f545dcc6
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
onMounted
,
watch
,
computed
,
unref
,
ref
,
nextTick
}
from
'vue'
import
{
computed
,
nextTick
,
onMounted
,
ref
,
unref
,
watch
}
from
'vue'
import
{
useRouter
}
from
'vue-router'
import
type
{
RouteLocationNormalizedLoaded
,
RouterLinkProps
}
from
'vue-router'
import
type
{
RouteLocationNormalizedLoaded
,
RouterLinkProps
}
from
'vue-router'
import
{
useRouter
}
from
'vue-router'
import
{
usePermissionStore
}
from
'@/store/modules/permission'
import
{
usePermissionStore
}
from
'@/store/modules/permission'
import
{
useTagsViewStore
}
from
'@/store/modules/tagsView'
import
{
useTagsViewStore
}
from
'@/store/modules/tagsView'
import
{
useAppStore
}
from
'@/store/modules/app'
import
{
useAppStore
}
from
'@/store/modules/app'
...
@@ -273,16 +273,28 @@ watch(
...
@@ -273,16 +273,28 @@ watch(
@click="move(-200)"
@click="move(-200)"
>
>
<Icon
<Icon
icon=
"ep:d-arrow-left"
color=
"var(--el-text-color-placeholder)"
:hover-color=
"isDark ? '#fff' : 'var(--el-color-black)'"
:hover-color=
"isDark ? '#fff' : 'var(--el-color-black)'"
color=
"var(--el-text-color-placeholder)"
icon=
"ep:d-arrow-left"
/>
/>
</span>
</span>
<div
class=
"flex-1 overflow-hidden"
>
<div
class=
"flex-1 overflow-hidden"
>
<ElScrollbar
ref=
"scrollbarRef"
class=
"h-full"
@
scroll=
"scroll"
>
<ElScrollbar
ref=
"scrollbarRef"
class=
"h-full"
@
scroll=
"scroll"
>
<div
class=
"h-[var(--tags-view-height)] flex"
>
<div
class=
"h-[var(--tags-view-height)] flex"
>
<ContextMenu
<ContextMenu
v-for=
"item in visitedViews"
:key=
"item.fullPath"
:ref=
"itemRefs.set"
:ref=
"itemRefs.set"
:class=
"[
`$
{prefixCls}__item`,
tagsViewImmerse ? `${prefixCls}__item--immerse` : '',
tagsViewIcon ? `${prefixCls}__item--icon` : '',
tagsViewImmerse
&&
tagsViewIcon ? `${prefixCls}__item--immerse--icon` : '',
item?.meta?.affix ? `${prefixCls}__item--affix` : '',
{
'is-active': isActive(item)
}
]"
:schema="[
:schema="[
{
{
icon: 'ep:refresh',
icon: 'ep:refresh',
...
@@ -340,26 +352,14 @@ watch(
...
@@ -340,26 +352,14 @@ watch(
}
}
}
}
]"
]"
v-for="item in visitedViews"
:key="item.fullPath"
:tag-item="item"
:tag-item="item"
:class="[
`${prefixCls}__item`,
tagsViewImmerse ? `${prefixCls}__item--immerse` : '',
tagsViewIcon ? `${prefixCls}__item--icon` : '',
tagsViewImmerse
&&
tagsViewIcon ? `${prefixCls}__item--immerse--icon` : '',
item?.meta?.affix ? `${prefixCls}__item--affix` : '',
{
'is-active': isActive(item)
}
]"
@visible-change="visibleChange"
@visible-change="visibleChange"
>
>
<div>
<div>
<router-link
:ref=
"tagLinksRefs.set"
:to=
"
{ ...item }" custom v-slot="{ navigate }"
>
<router-link
:ref=
"tagLinksRefs.set"
v-slot=
"
{ navigate }" :to="{ ...item }" custom
>
<div
<div
@
click=
"navigate"
:class=
"`h-full flex items-center justify-center whitespace-nowrap pl-15px $
{prefixCls}__item--label`"
:class=
"`h-full flex items-center justify-center whitespace-nowrap pl-15px $
{prefixCls}__item--label`"
@click="navigate"
>
>
<Icon
<Icon
v-if=
"
v-if=
"
...
@@ -376,9 +376,9 @@ watch(
...
@@ -376,9 +376,9 @@ watch(
{{
t
(
item
?.
meta
?.
title
as
string
)
}}
{{
t
(
item
?.
meta
?.
title
as
string
)
}}
<Icon
<Icon
:class=
"`$
{prefixCls}__item--close`"
:class=
"`$
{prefixCls}__item--close`"
:size="12"
color="#333"
color="#333"
icon="ep:close"
icon="ep:close"
:size="12"
@click.prevent.stop="closeSelectedTag(item)"
@click.prevent.stop="closeSelectedTag(item)"
/>
/>
</div>
</div>
...
@@ -394,9 +394,9 @@ watch(
...
@@ -394,9 +394,9 @@ watch(
@click="move(200)"
@click="move(200)"
>
>
<Icon
<Icon
icon=
"ep:d-arrow-right"
color=
"var(--el-text-color-placeholder)"
:hover-color=
"isDark ? '#fff' : 'var(--el-color-black)'"
:hover-color=
"isDark ? '#fff' : 'var(--el-color-black)'"
color=
"var(--el-text-color-placeholder)"
icon=
"ep:d-arrow-right"
/>
/>
</span>
</span>
<span
<span
...
@@ -405,13 +405,12 @@ watch(
...
@@ -405,13 +405,12 @@ watch(
@click="refreshSelectedTag(selectedTag)"
@click="refreshSelectedTag(selectedTag)"
>
>
<Icon
<Icon
icon=
"ep:refresh-right"
color=
"var(--el-text-color-placeholder)"
:hover-color=
"isDark ? '#fff' : 'var(--el-color-black)'"
:hover-color=
"isDark ? '#fff' : 'var(--el-color-black)'"
color=
"var(--el-text-color-placeholder)"
icon=
"ep:refresh-right"
/>
/>
</span>
</span>
<ContextMenu
<ContextMenu
trigger=
"click"
:schema=
"[
:schema=
"[
{
{
icon: 'ep:refresh',
icon: 'ep:refresh',
...
@@ -463,15 +462,16 @@ watch(
...
@@ -463,15 +462,16 @@ watch(
}
}
}
}
]"
]"
trigger="click"
>
>
<span
<span
:class=
"tagsViewImmerse ? '' : `$
{prefixCls}__tool`"
:class=
"tagsViewImmerse ? '' : `$
{prefixCls}__tool`"
class="block h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
class="block h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center"
>
>
<Icon
<Icon
icon=
"ep:menu"
color=
"var(--el-text-color-placeholder)"
:hover-color=
"isDark ? '#fff' : 'var(--el-color-black)'"
:hover-color=
"isDark ? '#fff' : 'var(--el-color-black)'"
color=
"var(--el-text-color-placeholder)"
icon=
"ep:menu"
/>
/>
</span>
</span>
</ContextMenu>
</ContextMenu>
...
@@ -532,6 +532,7 @@ $prefix-cls: #{$namespace}-tags-view;
...
@@ -532,6 +532,7 @@ $prefix-cls: #{$namespace}-tags-view;
display
:
none
;
display
:
none
;
transform
:
translate
(
0
,
-50%
);
transform
:
translate
(
0
,
-50%
);
}
}
&
:not
(.
#
{
$prefix-cls
}
__item--affix
)
:hover
{
&
:not
(.
#
{
$prefix-cls
}
__item--affix
)
:hover
{
.#{$prefix-cls
}
__item--close
{
.#{$prefix-cls
}
__item--close
{
display
:
block
;
display
:
block
;
...
@@ -553,6 +554,7 @@ $prefix-cls: #{$namespace}-tags-view;
...
@@ -553,6 +554,7 @@ $prefix-cls: #{$namespace}-tags-view;
color
:
var
(
--el-color-white
);
color
:
var
(
--el-color-white
);
background-color
:
var
(
--el-color-primary
);
background-color
:
var
(
--el-color-primary
);
border
:
1px
solid
var
(
--el-color-primary
);
border
:
1px
solid
var
(
--el-color-primary
);
.#{$prefix-cls
}
__item--close
{
.#{$prefix-cls
}
__item--close
{
:deep(span)
{
:deep(span)
{
color
:
var
(
--el-color-white
)
!important
;
color
:
var
(
--el-color-white
)
!important
;
...
@@ -568,9 +570,11 @@ $prefix-cls: #{$namespace}-tags-view;
...
@@ -568,9 +570,11 @@ $prefix-cls: #{$namespace}-tags-view;
border
:
none
!important
;
border
:
none
!important
;
-webkit-mask-box-image
:
url("data:image/svg+xml,%3Csvg width='68' height='34' viewBox='0 0 68 34' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m27,0c-7.99582,0 -11.95105,0.00205 -12,12l0,6c0,8.284 -0.48549,16.49691 -8.76949,16.49691l54.37857,-0.11145c-8.284,0 -8.60908,-8.10146 -8.60908,-16.38546l0,-6c0.11145,-12.08445 -4.38441,-12 -12,-12l-13,0z' fill='%23409eff'/%3E%3C/svg%3E")
-webkit-mask-box-image
:
url("data:image/svg+xml,%3Csvg width='68' height='34' viewBox='0 0 68 34' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='m27,0c-7.99582,0 -11.95105,0.00205 -12,12l0,6c0,8.284 -0.48549,16.49691 -8.76949,16.49691l54.37857,-0.11145c-8.284,0 -8.60908,-8.10146 -8.60908,-16.38546l0,-6c0.11145,-12.08445 -4.38441,-12 -12,-12l-13,0z' fill='%23409eff'/%3E%3C/svg%3E")
12
27
15
;
12
27
15
;
.#{$prefix-cls
}
__item--label
{
.#{$prefix-cls
}
__item--label
{
padding-left
:
35px
;
padding-left
:
35px
;
}
}
.
#
{
$prefix-cls
}
__item--close
{
.
#
{
$prefix-cls
}
__item--close
{
right
:
20px
;
right
:
20px
;
}
}
...
@@ -584,6 +588,7 @@ $prefix-cls: #{$namespace}-tags-view;
...
@@ -584,6 +588,7 @@ $prefix-cls: #{$namespace}-tags-view;
&:hover
{
&:hover
{
color
:
var
(
--el-color-white
);
color
:
var
(
--el-color-white
);
background-color
:
var
(
--el-color-primary
);
background-color
:
var
(
--el-color-primary
);
.#{$prefix-cls
}
__item--close
{
.#{$prefix-cls
}
__item--close
{
:deep(span)
{
:deep(span)
{
color
:
var
(
--el-color-white
)
!important
;
color
:
var
(
--el-color-white
)
!important
;
...
@@ -617,6 +622,7 @@ $prefix-cls: #{$namespace}-tags-view;
...
@@ -617,6 +622,7 @@ $prefix-cls: #{$namespace}-tags-view;
color
:
var
(
--el-color-white
);
color
:
var
(
--el-color-white
);
background-color
:
var
(
--el-color-primary
);
background-color
:
var
(
--el-color-primary
);
border
:
1px
solid
var
(
--el-color-primary
);
border
:
1px
solid
var
(
--el-color-primary
);
.#{$prefix-cls
}
__item--close
{
.#{$prefix-cls
}
__item--close
{
:deep(span)
{
:deep(span)
{
color
:
var
(
--el-color-white
)
!important
;
color
:
var
(
--el-color-white
)
!important
;
...
...
src/layout/components/UserInfo/src/UserInfo.vue
View file @
f545dcc6
...
@@ -23,7 +23,7 @@ const { getPrefixCls } = useDesign()
...
@@ -23,7 +23,7 @@ const { getPrefixCls } = useDesign()
const
prefixCls
=
getPrefixCls
(
'user-info'
)
const
prefixCls
=
getPrefixCls
(
'user-info'
)
const
avatar
=
computed
(()
=>
userStore
.
user
.
avatar
??
avatarImg
)
const
avatar
=
computed
(()
=>
userStore
.
user
.
avatar
||
avatarImg
)
const
userName
=
computed
(()
=>
userStore
.
user
.
nickname
??
'Admin'
)
const
userName
=
computed
(()
=>
userStore
.
user
.
nickname
??
'Admin'
)
// 锁定屏幕
// 锁定屏幕
...
...
src/layout/components/UserInfo/src/components/LockDialog.vue
View file @
f545dcc6
...
@@ -21,7 +21,7 @@ const props = defineProps({
...
@@ -21,7 +21,7 @@ const props = defineProps({
})
})
const
userStore
=
useUserStore
()
const
userStore
=
useUserStore
()
const
avatar
=
computed
(()
=>
userStore
.
user
.
avatar
??
avatarImg
)
const
avatar
=
computed
(()
=>
userStore
.
user
.
avatar
||
avatarImg
)
const
userName
=
computed
(()
=>
userStore
.
user
.
nickname
??
'Admin'
)
const
userName
=
computed
(()
=>
userStore
.
user
.
nickname
??
'Admin'
)
const
emit
=
defineEmits
([
'update:modelValue'
])
const
emit
=
defineEmits
([
'update:modelValue'
])
...
...
src/layout/components/UserInfo/src/components/LockPage.vue
View file @
f545dcc6
...
@@ -22,7 +22,7 @@ const showDate = ref(true)
...
@@ -22,7 +22,7 @@ const showDate = ref(true)
const
{
getPrefixCls
}
=
useDesign
()
const
{
getPrefixCls
}
=
useDesign
()
const
prefixCls
=
getPrefixCls
(
'lock-page'
)
const
prefixCls
=
getPrefixCls
(
'lock-page'
)
const
avatar
=
computed
(()
=>
userStore
.
user
.
avatar
??
avatarImg
)
const
avatar
=
computed
(()
=>
userStore
.
user
.
avatar
||
avatarImg
)
const
userName
=
computed
(()
=>
userStore
.
user
.
nickname
??
'Admin'
)
const
userName
=
computed
(()
=>
userStore
.
user
.
nickname
??
'Admin'
)
const
lockStore
=
useLockStore
()
const
lockStore
=
useLockStore
()
...
...
src/router/index.ts
View file @
f545dcc6
...
@@ -5,7 +5,7 @@ import remainingRouter from './modules/remaining'
...
@@ -5,7 +5,7 @@ import remainingRouter from './modules/remaining'
// 创建路由实例
// 创建路由实例
const
router
=
createRouter
({
const
router
=
createRouter
({
history
:
createWebHistory
(),
// createWebHashHistory URL带#,createWebHistory URL不带#
history
:
createWebHistory
(
import
.
meta
.
env
.
VITE_BASE_PATH
),
// createWebHashHistory URL带#,createWebHistory URL不带#
strict
:
true
,
strict
:
true
,
routes
:
remainingRouter
as
RouteRecordRaw
[],
routes
:
remainingRouter
as
RouteRecordRaw
[],
scrollBehavior
:
()
=>
({
left
:
0
,
top
:
0
})
scrollBehavior
:
()
=>
({
left
:
0
,
top
:
0
})
...
...
src/store/modules/app.ts
View file @
f545dcc6
import
{
defineStore
}
from
'pinia'
import
{
defineStore
}
from
'pinia'
import
{
store
}
from
'../index'
import
{
store
}
from
'../index'
import
{
setCssVar
,
humpToUnderline
}
from
'@/utils'
import
{
humpToUnderline
,
setCssVar
}
from
'@/utils'
import
{
ElMessage
}
from
'element-plus'
import
{
ElMessage
}
from
'element-plus'
import
{
CACHE_KEY
,
useCache
}
from
'@/hooks/web/useCache'
import
{
CACHE_KEY
,
useCache
}
from
'@/hooks/web/useCache'
import
{
ElementPlusSize
}
from
'@/types/elementPlus'
import
{
ElementPlusSize
}
from
'@/types/elementPlus'
...
...
src/utils/is.ts
View file @
f545dcc6
...
@@ -18,8 +18,8 @@ export const isObject = (val: any): val is Record<any, any> => {
...
@@ -18,8 +18,8 @@ export const isObject = (val: any): val is Record<any, any> => {
return
val
!==
null
&&
is
(
val
,
'Object'
)
return
val
!==
null
&&
is
(
val
,
'Object'
)
}
}
export
const
isEmpty
=
<
T
=
unknown
>
(
val
:
T
):
val
is
T
=>
{
export
const
isEmpty
=
(
val
:
any
):
boolean
=>
{
if
(
val
===
null
)
{
if
(
val
===
null
||
val
===
undefined
||
typeof
val
===
'undefined'
)
{
return
true
return
true
}
}
if
(
isArray
(
val
)
||
isString
(
val
))
{
if
(
isArray
(
val
)
||
isString
(
val
))
{
...
...
src/views/Login/Login.vue
View file @
f545dcc6
...
@@ -32,15 +32,16 @@
...
@@ -32,15 +32,16 @@
>
>
<!-- 右上角的主题、语言选择 -->
<!-- 右上角的主题、语言选择 -->
<div
<div
class=
"flex items-center justify-between text-white at-2xl:justify-end at-xl:justify-end"
class=
"flex items-center justify-between at-2xl:justify-end at-xl:justify-end"
style=
"color: var(--el-text-color-primary);"
>
>
<div
class=
"flex items-center at-2xl:hidden at-xl:hidden"
>
<div
class=
"flex items-center at-2xl:hidden at-xl:hidden"
>
<img
alt=
""
class=
"mr-10px h-48px w-48px"
src=
"@/assets/imgs/logo.png"
/>
<img
alt=
""
class=
"mr-10px h-48px w-48px"
src=
"@/assets/imgs/logo.png"
/>
<span
class=
"text-20px font-bold"
>
{{
underlineToHump
(
appStore
.
getTitle
)
}}
</span>
<span
class=
"text-20px font-bold"
>
{{
underlineToHump
(
appStore
.
getTitle
)
}}
</span>
</div>
</div>
<div
class=
"flex items-center justify-end space-x-10px h-48px"
>
<div
class=
"flex items-center justify-end space-x-10px h-48px"
>
<ThemeSwitch
/>
<ThemeSwitch
/>
<LocaleDropdown
class=
"dark:text-white lt-xl:text-white"
/>
<LocaleDropdown
/>
</div>
</div>
</div>
</div>
<!-- 右边的登录界面 -->
<!-- 右边的登录界面 -->
...
...
src/views/Login/SocialLogin.vue
View file @
f545dcc6
...
@@ -114,9 +114,9 @@
...
@@ -114,9 +114,9 @@
</el-checkbox>
</el-checkbox>
</el-col>
</el-col>
<el-col
:offset=
"6"
:span=
"12"
>
<el-col
:offset=
"6"
:span=
"12"
>
<el-link
style=
"float: right"
type=
"primary"
>
{{
<el-link
style=
"float: right"
type=
"primary"
t
(
'login.forgetPassword'
)
>
{{
t
(
'login.forgetPassword'
)
}}
}}
</el-link>
</el-link>
</el-col>
</el-col>
</el-row>
</el-row>
</el-form-item>
</el-form-item>
...
...
src/views/Login/components/QrCodeForm.vue
View file @
f545dcc6
<
template
>
<
template
>
<el-row
class=
"login-form"
v-show=
"getShow
"
style=
"margin-right: -10px; margin-left: -10px"
>
<el-row
v-show=
"getShow"
class=
"login-form
"
style=
"margin-right: -10px; margin-left: -10px"
>
<el-col
:span=
"24"
style=
"padding-right: 10px; padding-left: 10px"
>
<el-col
:span=
"24"
style=
"padding-right: 10px; padding-left: 10px"
>
<LoginFormTitle
style=
"width: 100%"
/>
<LoginFormTitle
style=
"width: 100%"
/>
</el-col>
</el-col>
...
...
src/views/ai/chat/index/components/message/MessageList.vue
View file @
f545dcc6
...
@@ -78,7 +78,7 @@ const userStore = useUserStore()
...
@@ -78,7 +78,7 @@ const userStore = useUserStore()
const
messageContainer
:
any
=
ref
(
null
)
const
messageContainer
:
any
=
ref
(
null
)
const
isScrolling
=
ref
(
false
)
//用于判断用户是否在滚动
const
isScrolling
=
ref
(
false
)
//用于判断用户是否在滚动
const
userAvatar
=
computed
(()
=>
userStore
.
user
.
avatar
??
userAvatarDefaultImg
)
const
userAvatar
=
computed
(()
=>
userStore
.
user
.
avatar
||
userAvatarDefaultImg
)
const
roleAvatar
=
computed
(()
=>
props
.
conversation
.
roleAvatar
??
roleAvatarDefaultImg
)
const
roleAvatar
=
computed
(()
=>
props
.
conversation
.
roleAvatar
??
roleAvatarDefaultImg
)
// 定义 props
// 定义 props
...
...
src/views/ai/mindmap/index/components/Right.vue
View file @
f545dcc6
...
@@ -4,7 +4,7 @@
...
@@ -4,7 +4,7 @@
<h3
class=
"m-0 px-7 shrink-0 flex items-center justify-between"
>
<h3
class=
"m-0 px-7 shrink-0 flex items-center justify-between"
>
<span>
思维导图预览
</span>
<span>
思维导图预览
</span>
<!-- 展示在右上角 -->
<!-- 展示在右上角 -->
<el-button
type=
"primary"
v-show=
"isEnd"
@
click=
"downloadImage"
size=
"small
"
>
<el-button
v-show=
"isEnd"
size=
"small"
type=
"primary"
@
click=
"downloadImage
"
>
<template
#
icon
>
<template
#
icon
>
<Icon
icon=
"ph:copy-bold"
/>
<Icon
icon=
"ph:copy-bold"
/>
</
template
>
</
template
>
...
@@ -20,14 +20,14 @@
...
@@ -20,14 +20,14 @@
</div>
</div>
<div
ref=
"mindMapRef"
class=
"wh-full"
>
<div
ref=
"mindMapRef"
class=
"wh-full"
>
<svg
ref=
"svgRef"
class=
"w-full"
:style=
"{ height: `${contentAreaHeight}px` }
"
/>
<svg
ref=
"svgRef"
:style=
"{ height: `${contentAreaHeight}px` }"
class=
"w-full
"
/>
<div
ref=
"toolBarRef"
class=
"absolute bottom-[10px] right-5"
></div>
<div
ref=
"toolBarRef"
class=
"absolute bottom-[10px] right-5"
></div>
</div>
</div>
</div>
</div>
</el-card>
</el-card>
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
lang=
"ts"
setup
>
import
{
Markmap
}
from
'markmap-view'
import
{
Markmap
}
from
'markmap-view'
import
{
Transformer
}
from
'markmap-lib'
import
{
Transformer
}
from
'markmap-lib'
import
{
Toolbar
}
from
'markmap-toolbar'
import
{
Toolbar
}
from
'markmap-toolbar'
...
@@ -137,6 +137,7 @@ defineExpose({
...
@@ -137,6 +137,7 @@ defineExpose({
height
:
0
;
height
:
0
;
}
}
}
}
.my-card
{
.my-card
{
display
:
flex
;
display
:
flex
;
flex-direction
:
column
;
flex-direction
:
column
;
...
@@ -149,13 +150,16 @@ defineExpose({
...
@@ -149,13 +150,16 @@ defineExpose({
@extend
.hide-scroll-bar;
@extend
.hide-scroll-bar;
}
}
}
}
//
markmap
的
tool
样式覆盖
//
markmap
的
tool
样式覆盖
:deep
(
.markmap
)
{
:deep
(
.markmap
)
{
width
:
100%
;
width
:
100%
;
}
}
:deep
(
.mm-toolbar-brand
)
{
:deep
(
.mm-toolbar-brand
)
{
display
:
none
;
display
:
none
;
}
}
:deep
(
.mm-toolbar
)
{
:deep
(
.mm-toolbar
)
{
display
:
flex
;
display
:
flex
;
flex-direction
:
row
;
flex-direction
:
row
;
...
...
src/views/ai/mindmap/index/index.vue
View file @
f545dcc6
...
@@ -3,9 +3,9 @@
...
@@ -3,9 +3,9 @@
<!--表单区域-->
<!--表单区域-->
<Left
<Left
ref=
"leftRef"
ref=
"leftRef"
:is-generating=
"isGenerating"
@
submit=
"submit"
@
submit=
"submit"
@
direct-generate=
"directGenerate"
@
direct-generate=
"directGenerate"
:is-generating=
"isGenerating"
/>
/>
<!--右边生成思维导图区域-->
<!--右边生成思维导图区域-->
<Right
<Right
...
@@ -18,7 +18,7 @@
...
@@ -18,7 +18,7 @@
</div>
</div>
</
template
>
</
template
>
<
script
setup
lang=
"ts"
>
<
script
lang=
"ts"
setup
>
import
Left
from
'./components/Left.vue'
import
Left
from
'./components/Left.vue'
import
Right
from
'./components/Right.vue'
import
Right
from
'./components/Right.vue'
import
{
AiMindMapApi
,
AiMindMapGenerateReqVO
}
from
'@/api/ai/mindmap'
import
{
AiMindMapApi
,
AiMindMapGenerateReqVO
}
from
'@/api/ai/mindmap'
...
...
src/views/ai/write/index/components/Left.vue
View file @
f545dcc6
...
@@ -2,8 +2,8 @@
...
@@ -2,8 +2,8 @@
<!-- 定义 tab 组件:撰写/回复等 -->
<!-- 定义 tab 组件:撰写/回复等 -->
<DefineTab
v-slot=
"
{ active, text, itemClick }">
<DefineTab
v-slot=
"
{ active, text, itemClick }">
<span
<span
class=
"inline-block w-1/2 rounded-full cursor-pointer text-center leading-[30px] relative z-1 text-[5C6370] hover:text-black"
:class=
"active ? 'text-black shadow-md' : 'hover:bg-[#DDDFE3]'"
:class=
"active ? 'text-black shadow-md' : 'hover:bg-[#DDDFE3]'"
class=
"inline-block w-1/2 rounded-full cursor-pointer text-center leading-[30px] relative z-1 text-[5C6370] hover:text-black"
@
click=
"itemClick"
@
click=
"itemClick"
>
>
{{
text
}}
{{
text
}}
...
@@ -14,9 +14,9 @@
...
@@ -14,9 +14,9 @@
<h3
class=
"mt-5 mb-3 flex items-center justify-between text-[14px]"
>
<h3
class=
"mt-5 mb-3 flex items-center justify-between text-[14px]"
>
<span>
{{
label
}}
</span>
<span>
{{
label
}}
</span>
<span
<span
@
click=
"hintClick"
v-if=
"hint"
v-if=
"hint"
class=
"flex items-center text-[12px] text-[#846af7] cursor-pointer select-none"
class=
"flex items-center text-[12px] text-[#846af7] cursor-pointer select-none"
@
click=
"hintClick"
>
>
<Icon
icon=
"ep:question-filled"
/>
<Icon
icon=
"ep:question-filled"
/>
{{
hint
}}
{{
hint
}}
...
@@ -29,17 +29,17 @@
...
@@ -29,17 +29,17 @@
<div
class=
"w-full pt-2 bg-[#f5f7f9] flex justify-center"
>
<div
class=
"w-full pt-2 bg-[#f5f7f9] flex justify-center"
>
<div
class=
"w-[303px] rounded-full bg-[#DDDFE3] p-1 z-10"
>
<div
class=
"w-[303px] rounded-full bg-[#DDDFE3] p-1 z-10"
>
<div
<div
class=
"flex items-center relative after:content-[''] after:block after:bg-white after:h-[30px] after:w-1/2 after:absolute after:top-0 after:left-0 after:transition-transform after:rounded-full"
:class=
"
:class=
"
selectedTab === AiWriteTypeEnum.REPLY && 'after:transform after:translate-x-[100%]'
selectedTab === AiWriteTypeEnum.REPLY && 'after:transform after:translate-x-[100%]'
"
"
class=
"flex items-center relative after:content-[''] after:block after:bg-white after:h-[30px] after:w-1/2 after:absolute after:top-0 after:left-0 after:transition-transform after:rounded-full"
>
>
<ReuseTab
<ReuseTab
v-for=
"tab in tabs"
v-for=
"tab in tabs"
:key=
"tab.value"
:key=
"tab.value"
:text=
"tab.text"
:active=
"tab.value === selectedTab"
:active=
"tab.value === selectedTab"
:itemClick=
"() => switchTab(tab.value)"
:itemClick=
"() => switchTab(tab.value)"
:text=
"tab.text"
/>
/>
</div>
</div>
</div>
</div>
...
@@ -49,36 +49,36 @@
...
@@ -49,36 +49,36 @@
>
>
<div>
<div>
<template
v-if=
"selectedTab === 1"
>
<template
v-if=
"selectedTab === 1"
>
<ReuseLabel
label=
"写作内容"
hint=
"示例"
:hint-click=
"() => example('write')
"
/>
<ReuseLabel
:hint-click=
"() => example('write')"
hint=
"示例"
label=
"写作内容
"
/>
<el-input
<el-input
type=
"textarea"
:rows=
"5"
:maxlength=
"500"
v-model=
"formData.prompt"
v-model=
"formData.prompt"
:maxlength=
"500"
:rows=
"5"
placeholder=
"请输入写作内容"
placeholder=
"请输入写作内容"
showWordLimit
showWordLimit
type=
"textarea"
/>
/>
</
template
>
</
template
>
<
template
v-else
>
<
template
v-else
>
<ReuseLabel
label=
"原文"
hint=
"示例"
:hint-click=
"() => example('reply')
"
/>
<ReuseLabel
:hint-click=
"() => example('reply')"
hint=
"示例"
label=
"原文
"
/>
<el-input
<el-input
type=
"textarea"
:rows=
"5"
:maxlength=
"500"
v-model=
"formData.originalContent"
v-model=
"formData.originalContent"
:maxlength=
"500"
:rows=
"5"
placeholder=
"请输入原文"
placeholder=
"请输入原文"
showWordLimit
showWordLimit
type=
"textarea"
/>
/>
<ReuseLabel
label=
"回复内容"
/>
<ReuseLabel
label=
"回复内容"
/>
<el-input
<el-input
type=
"textarea"
:rows=
"5"
:maxlength=
"500"
v-model=
"formData.prompt"
v-model=
"formData.prompt"
:maxlength=
"500"
:rows=
"5"
placeholder=
"请输入回复内容"
placeholder=
"请输入回复内容"
showWordLimit
showWordLimit
type=
"textarea"
/>
/>
</
template
>
</
template
>
...
@@ -93,14 +93,14 @@
...
@@ -93,14 +93,14 @@
<div
class=
"flex items-center justify-center mt-3"
>
<div
class=
"flex items-center justify-center mt-3"
>
<el-button
:disabled=
"isWriting"
@
click=
"reset"
>
重置
</el-button>
<el-button
:disabled=
"isWriting"
@
click=
"reset"
>
重置
</el-button>
<el-button
:loading=
"isWriting"
@
click=
"submit"
color=
"#846af7
"
>
生成
</el-button>
<el-button
:loading=
"isWriting"
color=
"#846af7"
@
click=
"submit
"
>
生成
</el-button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
lang=
"ts"
setup
>
import
{
createReusableTemplate
}
from
'@vueuse/core'
import
{
createReusableTemplate
}
from
'@vueuse/core'
import
{
ref
}
from
'vue'
import
{
ref
}
from
'vue'
import
Tag
from
'./Tag.vue'
import
Tag
from
'./Tag.vue'
...
...
src/views/bpm/processInstance/detail/ProcessInstance
BtnConatiner
.vue
→
src/views/bpm/processInstance/detail/ProcessInstance
OperationButton
.vue
View file @
f545dcc6
<
template
>
<
template
>
<el-affix
target=
".formCol"
position=
"bottom"
class=
"h-50px"
v-if=
"runningTask?.id"
>
<el-divider
class=
"!mb-8px !mt-0"
/>
<div
<div
class=
"pl-50px
text-14px flex items-center color-#32373c dark:color-#fff font-bold btn-container"
class=
"h-50px position-fixed bottom-10
text-14px flex items-center color-#32373c dark:color-#fff font-bold btn-container"
>
>
<el-popover
:visible=
"passVisible"
placement=
"top-end"
:width=
"500"
trigger=
"click"
>
<el-popover
:visible=
"passVisible"
placement=
"top-end"
:width=
"500"
trigger=
"click"
>
<template
#
reference
>
<template
#
reference
>
...
@@ -27,9 +25,7 @@
...
@@ -27,9 +25,7 @@
</el-form-item>
</el-form-item>
<el-card
v-if=
"runningTask.formId > 0"
class=
"mb-15px !-mt-10px"
>
<el-card
v-if=
"runningTask.formId > 0"
class=
"mb-15px !-mt-10px"
>
<
template
#
header
>
<
template
#
header
>
<span
class=
"el-icon-picture-outline"
>
<span
class=
"el-icon-picture-outline"
>
填写表单【
{{
runningTask
?.
formName
}}
】
</span>
填写表单【
{{
runningTask
?.
formName
}}
】
</span>
</
template
>
</
template
>
<form-create
<form-create
v-model=
"approveForm.value"
v-model=
"approveForm.value"
...
@@ -84,9 +80,7 @@
...
@@ -84,9 +80,7 @@
</el-form-item>
</el-form-item>
<el-card
v-if=
"runningTask.formId > 0"
class=
"mb-15px !-mt-10px"
>
<el-card
v-if=
"runningTask.formId > 0"
class=
"mb-15px !-mt-10px"
>
<
template
#
header
>
<
template
#
header
>
<span
class=
"el-icon-picture-outline"
>
<span
class=
"el-icon-picture-outline"
>
填写表单【
{{
runningTask
?.
formName
}}
】
</span>
填写表单【
{{
runningTask
?.
formName
}}
】
</span>
</
template
>
</
template
>
<form-create
<form-create
v-model=
"approveForm.value"
v-model=
"approveForm.value"
...
@@ -126,7 +120,6 @@
...
@@ -126,7 +120,6 @@
<div
@
click=
"handleSign"
>
<Icon
:size=
"14"
icon=
"ep:plus"
/>
加签
</div>
<div
@
click=
"handleSign"
>
<Icon
:size=
"14"
icon=
"ep:plus"
/>
加签
</div>
<div
@
click=
"handleBack"
>
<Icon
:size=
"14"
icon=
"fa:mail-reply"
/>
退回
</div>
<div
@
click=
"handleBack"
>
<Icon
:size=
"14"
icon=
"fa:mail-reply"
/>
退回
</div>
</div>
</div>
</el-affix>
<!-- 弹窗:转派审批人 -->
<!-- 弹窗:转派审批人 -->
<TaskTransferForm
ref=
"taskTransferFormRef"
@
success=
"getDetail"
/>
<TaskTransferForm
ref=
"taskTransferFormRef"
@
success=
"getDetail"
/>
<!-- 弹窗:回退节点 -->
<!-- 弹窗:回退节点 -->
...
...
src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue
0 → 100644
View file @
f545dcc6
<
template
>
<el-timeline
class=
"pt-20px"
>
<el-timeline-item
v-for=
"(activity, index) in mockData"
:key=
"index"
size=
"large"
>
<div
class=
"flex flex-col items-start"
>
<div
class=
"font-bold"
>
{{
activity
.
name
}}
</div>
<div
class=
"color-#a1a6ae text-12px mb-10px"
>
{{
activity
.
assigneeUser
.
nickname
}}
</div>
<div
v-if=
"activity.opinion"
class=
"text-#a5a5a5 text-12px w-100%"
>
<div
class=
"mb-5px"
>
审批意见:
</div>
<div
class=
"w-100% border-1px border-#a5a5a5 border-dashed rounded py-5px px-15px text-#2d2d2d"
>
{{
activity
.
opinion
}}
</div>
</div>
<div
v-if=
"activity.createTime"
class=
"text-#a5a5a5 text-13px"
>
{{
formatDate
(
activity
.
createTime
)
}}
</div>
</div>
<!-- 该节点用户的头像 -->
<template
#
dot
>
<div
class=
"w-35px h-35px position-relative"
>
<img
src=
"@/assets/imgs/avatar.jpg"
class=
"rounded-full w-full h-full position-absolute bottom-6px right-12px"
alt=
""
/>
<div
class=
"position-absolute top-16px left-8px bg-#fff rounded-full flex items-center content-center p-2px"
>
<Icon
:size=
"12"
:icon=
"optIconMap[activity.status]?.icon"
:color=
"optIconMap[activity.status]?.color"
/>
</div>
</div>
</
template
>
</el-timeline-item>
</el-timeline>
</template>
<
script
lang=
"ts"
setup
>
import
{
formatDate
}
from
'@/utils/formatTime'
import
{
propTypes
}
from
'@/utils/propTypes'
defineOptions
({
name
:
'BpmProcessInstanceTimeline'
})
defineProps
({
tasks
:
propTypes
.
array
// 流程任务的数组
})
const
optIconMap
=
{
// 审批中
'1'
:
{
color
:
'#00b32a'
,
icon
:
'fa-solid:clock'
},
// 审批通过
'2'
:
{
color
:
'#00b32a'
,
icon
:
'fa-solid:check-circle'
},
// 审批不通过
'3'
:
{
color
:
'#f46b6c'
,
icon
:
'fa-solid:times-circle'
}
}
const
mockData
:
any
=
[
{
id
:
'fe1190ee-68c3-11ef-9c7d-00a6181404fd'
,
name
:
'发起人'
,
createTime
:
1725237646192
,
endTime
:
null
,
durationInMillis
:
null
,
status
:
1
,
reason
:
null
,
ownerUser
:
null
,
assigneeUser
:
{
id
:
104
,
nickname
:
'测试号'
,
deptId
:
107
,
deptName
:
'运维部门'
},
taskDefinitionKey
:
'task-01'
,
processInstanceId
:
'fe0c60c6-68c3-11ef-9c7d-00a6181404fd'
,
processInstance
:
{
id
:
'fe0c60c6-68c3-11ef-9c7d-00a6181404fd'
,
name
:
'oa_leave'
,
createTime
:
null
,
processDefinitionId
:
'oa_leave:1:6e5ac269-5f87-11ef-bdb6-00a6181404fd'
,
startUser
:
null
},
parentTaskId
:
null
,
children
:
null
,
formId
:
null
,
formName
:
null
,
formConf
:
null
,
formFields
:
null
,
formVariables
:
null
},
{
id
:
'fe1190ee-68c3-11ef-9c7d-00a6181404fd'
,
name
:
'领导审批'
,
createTime
:
1725237646192
,
endTime
:
null
,
durationInMillis
:
null
,
status
:
2
,
reason
:
null
,
ownerUser
:
null
,
assigneeUser
:
{
id
:
104
,
nickname
:
'领导'
,
deptId
:
107
,
deptName
:
'运维部门'
},
taskDefinitionKey
:
'task-01'
,
processInstanceId
:
'fe0c60c6-68c3-11ef-9c7d-00a6181404fd'
,
processInstance
:
{
id
:
'fe0c60c6-68c3-11ef-9c7d-00a6181404fd'
,
name
:
'oa_leave'
,
createTime
:
null
,
processDefinitionId
:
'oa_leave:1:6e5ac269-5f87-11ef-bdb6-00a6181404fd'
,
startUser
:
null
},
parentTaskId
:
null
,
children
:
null
,
formId
:
null
,
formName
:
null
,
formConf
:
null
,
formFields
:
null
,
formVariables
:
null
},
{
id
:
'fe1190ee-68c3-11ef-9c7d-00a6181404fd'
,
name
:
'财务总监审核'
,
createTime
:
1725237646192
,
endTime
:
null
,
durationInMillis
:
null
,
status
:
3
,
reason
:
null
,
ownerUser
:
null
,
assigneeUser
:
{
id
:
104
,
nickname
:
'财务总监'
,
deptId
:
107
,
deptName
:
'运维部门'
},
taskDefinitionKey
:
'task-01'
,
processInstanceId
:
'fe0c60c6-68c3-11ef-9c7d-00a6181404fd'
,
processInstance
:
{
id
:
'fe0c60c6-68c3-11ef-9c7d-00a6181404fd'
,
name
:
'oa_leave'
,
createTime
:
null
,
processDefinitionId
:
'oa_leave:1:6e5ac269-5f87-11ef-bdb6-00a6181404fd'
,
startUser
:
null
},
parentTaskId
:
null
,
children
:
null
,
formId
:
null
,
formName
:
null
,
formConf
:
null
,
formFields
:
null
,
formVariables
:
null
}
]
</
script
>
src/views/bpm/processInstance/detail/index_new.vue
View file @
f545dcc6
...
@@ -46,61 +46,18 @@
...
@@ -46,61 +46,18 @@
<BusinessFormComponent
:id=
"processInstance.businessKey"
/>
<BusinessFormComponent
:id=
"processInstance.businessKey"
/>
</div>
</div>
</div>
</div>
<!-- 操作栏按钮 -->
<!-- 操作栏按钮 -->
<!-- TODO @GoldenZqqq:ProcessInstanceOperationButton,操作按钮。不叫 Container 会好点点,和后端也更统一 -->
<ProcessInstanceOperationButton
<ProcessInstanceBtnConatiner
ref=
"operationButtonRef"
ref=
"processInstanceBtnRef"
:processInstance=
"processInstance"
:processInstance=
"processInstance"
:userOptions=
"userOptions"
:userOptions=
"userOptions"
@
success=
"getDetail"
@
success=
"getDetail"
/>
/>
</el-col>
</el-col>
<el-col
:span=
"6"
>
<el-col
:span=
"6"
>
<!-- TODO @GoldenZqqq:后续这个,也拆个小组件出来 -->
<!-- 审批记录时间线 -->
<el-timeline
class=
"pt-20px"
>
<ProcessInstanceTimeline
:process-instance=
"processInstance"
:tasks=
"tasks"
/>
<el-timeline-item
type=
"primary"
size=
"large"
>
<div
class=
"flex flex-col items-start gap-2"
>
<div
class=
"font-bold"
>
发起人:
{{
processInstance
?.
startUser
?.
nickname
}}
</div>
<el-tag
type=
"success"
>
发起
</el-tag>
<div
class=
"text-#a5a5a5 text-12px"
>
发起时间:
{{
formatDate
(
processInstance
.
startTime
)
}}
</div>
</div>
</el-timeline-item>
<el-timeline-item
v-for=
"(activity, index) in tasks"
:key=
"index"
type=
"primary"
size=
"large"
>
<div
class=
"flex flex-col items-start gap-2"
>
<div
class=
"font-bold"
>
审批人:
{{
activity
.
assigneeUser
?.
nickname
}}
</div>
<dict-tag
:type=
"DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS"
:value=
"activity.status"
/>
<!-- TODO:暂无该字段 -->
<div
v-if=
"activity.receiveTime"
class=
"text-#a5a5a5 text-12px"
>
接收时间:
{{
formatDate
(
activity
.
receiveTime
)
}}
</div>
<div
v-if=
"activity.createTime"
class=
"text-#a5a5a5 text-12px"
>
审批时间:
{{
formatDate
(
activity
.
createTime
)
}}
</div>
<div
v-if=
"activity.opinion"
class=
"text-#a5a5a5 text-12px w-100%"
>
<div
class=
"mb-5px"
>
审批意见:
</div>
<div
class=
"w-100% border-1px border-#a5a5a5 border-dashed rounded py-5px px-15px text-#2d2d2d"
>
{{
activity
.
opinion
}}
</div>
</div>
</div>
<!-- 该节点用户的头像 -->
<!--
<template
#
dot
>
<img
:src=
"activity?.avatar"
alt=
""
/>
</
template
>
-->
</el-timeline-item>
</el-timeline>
</el-col>
</el-col>
</el-row>
</el-row>
</el-tab-pane>
</el-tab-pane>
...
@@ -138,6 +95,8 @@ import * as ProcessInstanceApi from '@/api/bpm/processInstance'
...
@@ -138,6 +95,8 @@ import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import
*
as
TaskApi
from
'@/api/bpm/task'
import
*
as
TaskApi
from
'@/api/bpm/task'
import
ProcessInstanceBpmnViewer
from
'./ProcessInstanceBpmnViewer.vue'
import
ProcessInstanceBpmnViewer
from
'./ProcessInstanceBpmnViewer.vue'
import
ProcessInstanceTaskList
from
'./ProcessInstanceTaskList.vue'
import
ProcessInstanceTaskList
from
'./ProcessInstanceTaskList.vue'
import
ProcessInstanceOperationButton
from
'./ProcessInstanceOperationButton.vue'
import
ProcessInstanceTimeline
from
'./ProcessInstanceTimeline.vue'
import
{
registerComponent
}
from
'@/utils/routerHelper'
import
{
registerComponent
}
from
'@/utils/routerHelper'
import
*
as
UserApi
from
'@/api/system/user'
import
*
as
UserApi
from
'@/api/system/user'
import
audit1
from
'@/assets/svgs/bpm/audit1.svg'
import
audit1
from
'@/assets/svgs/bpm/audit1.svg'
...
@@ -151,7 +110,7 @@ const message = useMessage() // 消息弹窗
...
@@ -151,7 +110,7 @@ const message = useMessage() // 消息弹窗
const
id
=
query
.
id
as
unknown
as
string
// 流程实例的编号
const
id
=
query
.
id
as
unknown
as
string
// 流程实例的编号
const
processInstanceLoading
=
ref
(
false
)
// 流程实例的加载中
const
processInstanceLoading
=
ref
(
false
)
// 流程实例的加载中
const
processInstance
=
ref
<
any
>
({})
// 流程实例
const
processInstance
=
ref
<
any
>
({})
// 流程实例
const
processInstanceBt
nRef
=
ref
()
const
operationButto
nRef
=
ref
()
const
bpmnXml
=
ref
(
''
)
// BPMN XML
const
bpmnXml
=
ref
(
''
)
// BPMN XML
const
tasksLoad
=
ref
(
true
)
// 任务的加载中
const
tasksLoad
=
ref
(
true
)
// 任务的加载中
const
tasks
=
ref
<
any
[]
>
([])
// 任务列表
const
tasks
=
ref
<
any
[]
>
([])
// 任务列表
...
@@ -244,7 +203,7 @@ const getTaskList = async () => {
...
@@ -244,7 +203,7 @@ const getTaskList = async () => {
})
})
// 获得需要自己审批的任务
// 获得需要自己审批的任务
processInstanceBtnRef
.
value
.
loadRunningTask
(
tasks
.
value
)
operationButtonRef
.
value
?
.
loadRunningTask
(
tasks
.
value
)
}
finally
{
}
finally
{
tasksLoad
.
value
=
false
tasksLoad
.
value
=
false
}
}
...
...
src/views/mall/home/components/OperationDataCard.vue
View file @
f545dcc6
...
@@ -11,9 +11,9 @@
...
@@ -11,9 +11,9 @@
@
click=
"handleClick(item.routerName)"
@
click=
"handleClick(item.routerName)"
>
>
<CountTo
<CountTo
:prefix=
"item.prefix"
:end-val=
"item.value"
:decimals=
"item.decimals"
:decimals=
"item.decimals"
:end-val=
"item.value"
:prefix=
"item.prefix"
class=
"text-3xl"
class=
"text-3xl"
/>
/>
<span
class=
"text-center"
>
{{ item.name }}
</span>
<span
class=
"text-center"
>
{{ item.name }}
</span>
...
...
src/views/mall/product/spu/components/SkuList.vue
View file @
f545dcc6
...
@@ -180,17 +180,17 @@
...
@@ -180,17 +180,17 @@
</el-table-column>
</el-table-column>
<el-table-column
align=
"center"
label=
"销售价(元)"
min-width=
"80"
>
<el-table-column
align=
"center"
label=
"销售价(元)"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
<
template
#
default=
"{ row }"
>
{{
row
.
price
}}
{{
formatToFraction
(
row
.
price
)
}}
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
align=
"center"
label=
"市场价(元)"
min-width=
"80"
>
<el-table-column
align=
"center"
label=
"市场价(元)"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
<
template
#
default=
"{ row }"
>
{{
row
.
marketPrice
}}
{{
formatToFraction
(
row
.
marketPrice
)
}}
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
align=
"center"
label=
"成本价(元)"
min-width=
"80"
>
<el-table-column
align=
"center"
label=
"成本价(元)"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
<
template
#
default=
"{ row }"
>
{{
row
.
costPrice
}}
{{
formatToFraction
(
row
.
costPrice
)
}}
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
align=
"center"
label=
"库存"
min-width=
"80"
>
<el-table-column
align=
"center"
label=
"库存"
min-width=
"80"
>
...
@@ -211,12 +211,12 @@
...
@@ -211,12 +211,12 @@
<
template
v-if=
"formData!.subCommissionType"
>
<
template
v-if=
"formData!.subCommissionType"
>
<el-table-column
align=
"center"
label=
"一级返佣(元)"
min-width=
"80"
>
<el-table-column
align=
"center"
label=
"一级返佣(元)"
min-width=
"80"
>
<template
#
default=
"
{ row }">
<template
#
default=
"
{ row }">
{{
row
.
firstBrokeragePrice
}}
{{
formatToFraction
(
row
.
firstBrokeragePrice
)
}}
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
align=
"center"
label=
"二级返佣(元)"
min-width=
"80"
>
<el-table-column
align=
"center"
label=
"二级返佣(元)"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
<
template
#
default=
"{ row }"
>
{{
row
.
secondBrokeragePrice
}}
{{
formatToFraction
(
row
.
secondBrokeragePrice
)
}}
</
template
>
</
template
>
</el-table-column>
</el-table-column>
</template>
</template>
...
@@ -260,17 +260,17 @@
...
@@ -260,17 +260,17 @@
</el-table-column>
</el-table-column>
<el-table-column
align=
"center"
label=
"销售价(元)"
min-width=
"80"
>
<el-table-column
align=
"center"
label=
"销售价(元)"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
<
template
#
default=
"{ row }"
>
{{
row
.
price
}}
{{
formatToFraction
(
row
.
price
)
}}
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
align=
"center"
label=
"市场价(元)"
min-width=
"80"
>
<el-table-column
align=
"center"
label=
"市场价(元)"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
<
template
#
default=
"{ row }"
>
{{
row
.
marketPrice
}}
{{
formatToFraction
(
row
.
marketPrice
)
}}
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
align=
"center"
label=
"成本价(元)"
min-width=
"80"
>
<el-table-column
align=
"center"
label=
"成本价(元)"
min-width=
"80"
>
<
template
#
default=
"{ row }"
>
<
template
#
default=
"{ row }"
>
{{
row
.
costPrice
}}
{{
formatToFraction
(
row
.
costPrice
)
}}
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
align=
"center"
label=
"库存"
min-width=
"80"
>
<el-table-column
align=
"center"
label=
"库存"
min-width=
"80"
>
...
@@ -284,7 +284,7 @@
...
@@ -284,7 +284,7 @@
</template>
</template>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
PropType
,
Ref
}
from
'vue'
import
{
PropType
,
Ref
}
from
'vue'
import
{
copyValueToTarget
}
from
'@/utils'
import
{
copyValueToTarget
,
formatToFraction
}
from
'@/utils'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
UploadImg
}
from
'@/components/UploadFile'
import
{
UploadImg
}
from
'@/components/UploadFile'
import
type
{
Property
,
Sku
,
Spu
}
from
'@/api/mall/product/spu'
import
type
{
Property
,
Sku
,
Spu
}
from
'@/api/mall/product/spu'
...
...
src/views/mall/product/spu/components/SpuShowcase.vue
View file @
f545dcc6
...
@@ -85,6 +85,7 @@ const openSpuTableSelect = () => {
...
@@ -85,6 +85,7 @@ const openSpuTableSelect = () => {
/**
/**
* 选择商品后触发
* 选择商品后触发
*
* @param spus 选中的商品列表
* @param spus 选中的商品列表
*/
*/
const
handleSpuSelected
=
(
spus
:
ProductSpuApi
.
Spu
|
ProductSpuApi
.
Spu
[])
=>
{
const
handleSpuSelected
=
(
spus
:
ProductSpuApi
.
Spu
|
ProductSpuApi
.
Spu
[])
=>
{
...
@@ -94,6 +95,7 @@ const handleSpuSelected = (spus: ProductSpuApi.Spu | ProductSpuApi.Spu[]) => {
...
@@ -94,6 +95,7 @@ const handleSpuSelected = (spus: ProductSpuApi.Spu | ProductSpuApi.Spu[]) => {
/**
/**
* 删除商品
* 删除商品
*
* @param index 商品索引
* @param index 商品索引
*/
*/
const
handleRemoveSpu
=
(
index
:
number
)
=>
{
const
handleRemoveSpu
=
(
index
:
number
)
=>
{
...
...
src/views/mall/product/spu/form/ProductAttributes.vue
View file @
f545dcc6
...
@@ -19,19 +19,19 @@
...
@@ -19,19 +19,19 @@
{{
value
.
name
}}
{{
value
.
name
}}
</el-tag>
</el-tag>
<el-select
<el-select
v-show=
"inputVisible(index)"
:id=
"`input$
{index}`"
:id=
"`input$
{index}`"
:ref="setInputRef"
:ref="setInputRef"
v-show="inputVisible(index)"
v-model="inputValue"
v-model="inputValue"
filterable
:reserve-keyword="false"
allow-create
allow-create
class="!w-30"
default-first-option
default-first-option
:reserve-keyword="false"
filterable
size="small"
size="small"
class="!w-30"
@blur="handleInputConfirm(index, item.id)"
@blur="handleInputConfirm(index, item.id)"
@keyup.enter="handleInputConfirm(index, item.id)"
@change="handleInputConfirm(index, item.id)"
@change="handleInputConfirm(index, item.id)"
@keyup.enter="handleInputConfirm(index, item.id)"
>
>
<el-option
<el-option
v-for=
"item2 in attributeOptions"
v-for=
"item2 in attributeOptions"
...
...
src/views/mall/product/spu/form/ProductPropertyAddForm.vue
View file @
f545dcc6
...
@@ -12,12 +12,12 @@
...
@@ -12,12 +12,12 @@
<el-form-item
label=
"属性名称"
prop=
"name"
>
<el-form-item
label=
"属性名称"
prop=
"name"
>
<el-select
<el-select
v-model=
"formData.name"
v-model=
"formData.name"
filterable
:reserve-keyword=
"false"
allow-create
allow-create
class=
"!w-360px"
default-first-option
default-first-option
:reserve-keyword=
"false"
filterable
placeholder=
"请选择属性名称。如果不存在,可手动输入选择"
placeholder=
"请选择属性名称。如果不存在,可手动输入选择"
class=
"!w-360px"
>
>
<el-option
<el-option
v-for=
"item in attributeOptions"
v-for=
"item in attributeOptions"
...
...
src/views/mall/product/spu/form/SkuForm.vue
View file @
f545dcc6
...
@@ -2,11 +2,11 @@
...
@@ -2,11 +2,11 @@
<
template
>
<
template
>
<el-form
<el-form
ref=
"formRef"
ref=
"formRef"
v-loading=
"formLoading"
:disabled=
"isDetail"
:disabled=
"isDetail"
:model=
"formData"
:model=
"formData"
:rules=
"rules"
:rules=
"rules"
label-width=
"120px"
label-width=
"120px"
v-loading=
"formLoading"
>
>
<el-form-item
label=
"分销类型"
props=
"subCommissionType"
>
<el-form-item
label=
"分销类型"
props=
"subCommissionType"
>
<el-radio-group
<el-radio-group
...
...
src/views/mall/promotion/combination/components/CombinationShowcase.vue
0 → 100644
View file @
f545dcc6
<
template
>
<div
class=
"flex flex-wrap items-center gap-8px"
>
<div
v-for=
"(combinationActivity, index) in Activitys"
:key=
"combinationActivity.id"
class=
"select-box spu-pic"
>
<el-tooltip
:content=
"combinationActivity.name"
>
<div
class=
"relative h-full w-full"
>
<el-image
:src=
"combinationActivity.picUrl"
class=
"h-full w-full"
/>
<Icon
v-show=
"!disabled"
class=
"del-icon"
icon=
"ep:circle-close-filled"
@
click=
"handleRemoveActivity(index)"
/>
</div>
</el-tooltip>
</div>
<el-tooltip
content=
"选择活动"
v-if=
"canAdd"
>
<div
class=
"select-box"
@
click=
"openCombinationActivityTableSelect"
>
<Icon
icon=
"ep:plus"
/>
</div>
</el-tooltip>
</div>
<!-- 拼团活动选择对话框(表格形式) -->
<CombinationTableSelect
ref=
"combinationActivityTableSelectRef"
:multiple=
"limit != 1"
@
change=
"handleActivitySelected"
/>
</
template
>
<
script
lang=
"ts"
setup
>
import
*
as
CombinationActivityApi
from
'@/api/mall/promotion/combination/combinationActivity'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
oneOfType
}
from
'vue-types'
import
{
isArray
}
from
'@/utils/is'
import
CombinationTableSelect
from
'@/views/mall/promotion/combination/components/CombinationTableSelect.vue'
// 活动橱窗,一般用于装修时使用
// 提供功能:展示活动列表、添加活动、删除活动
defineOptions
({
name
:
'CombinationShowcase'
})
const
props
=
defineProps
({
modelValue
:
oneOfType
<
number
|
Array
<
number
>>
([
Number
,
Array
]).
isRequired
,
// 限制数量:默认不限制
limit
:
propTypes
.
number
.
def
(
Number
.
MAX_VALUE
),
disabled
:
propTypes
.
bool
.
def
(
false
)
})
// 计算是否可以添加
const
canAdd
=
computed
(()
=>
{
// 情况一:禁用时不可以添加
if
(
props
.
disabled
)
return
false
// 情况二:未指定限制数量时,可以添加
if
(
!
props
.
limit
)
return
true
// 情况三:检查已添加数量是否小于限制数量
return
Activitys
.
value
.
length
<
props
.
limit
})
// 拼团活动列表
const
Activitys
=
ref
<
CombinationActivityApi
.
CombinationActivityVO
[]
>
([])
watch
(
()
=>
props
.
modelValue
,
async
()
=>
{
const
ids
=
isArray
(
props
.
modelValue
)
?
// 情况一:多选
props
.
modelValue
:
// 情况二:单选
props
.
modelValue
?
[
props
.
modelValue
]
:
[]
// 不需要返显
if
(
ids
.
length
===
0
)
{
Activitys
.
value
=
[]
return
}
// 只有活动发生变化之后,才会查询活动
if
(
Activitys
.
value
.
length
===
0
||
Activitys
.
value
.
some
((
combinationActivity
)
=>
!
ids
.
includes
(
combinationActivity
.
id
!
))
)
{
Activitys
.
value
=
await
CombinationActivityApi
.
getCombinationActivityListByIds
(
ids
)
}
},
{
immediate
:
true
}
)
/** 活动表格选择对话框 */
const
combinationActivityTableSelectRef
=
ref
()
// 打开对话框
const
openCombinationActivityTableSelect
=
()
=>
{
combinationActivityTableSelectRef
.
value
.
open
(
Activitys
.
value
)
}
/**
* 选择活动后触发
* @param activityVOs 选中的活动列表
*/
const
handleActivitySelected
=
(
activityVOs
:
|
CombinationActivityApi
.
CombinationActivityVO
|
CombinationActivityApi
.
CombinationActivityVO
[]
)
=>
{
Activitys
.
value
=
isArray
(
activityVOs
)
?
activityVOs
:
[
activityVOs
]
emitActivityChange
()
}
/**
* 删除活动
* @param index 活动索引
*/
const
handleRemoveActivity
=
(
index
:
number
)
=>
{
Activitys
.
value
.
splice
(
index
,
1
)
emitActivityChange
()
}
const
emit
=
defineEmits
([
'update:modelValue'
,
'change'
])
const
emitActivityChange
=
()
=>
{
if
(
props
.
limit
===
1
)
{
const
combinationActivity
=
Activitys
.
value
.
length
>
0
?
Activitys
.
value
[
0
]
:
null
emit
(
'update:modelValue'
,
combinationActivity
?.
id
||
0
)
emit
(
'change'
,
combinationActivity
)
}
else
{
emit
(
'update:modelValue'
,
Activitys
.
value
.
map
((
combinationActivity
)
=>
combinationActivity
.
id
)
)
emit
(
'change'
,
Activitys
.
value
)
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.select-box
{
display
:
flex
;
width
:
60px
;
height
:
60px
;
border
:
1px
dashed
var
(
--el-border-color-darker
);
border-radius
:
8px
;
align-items
:
center
;
justify-content
:
center
;
cursor
:
pointer
;
}
.spu-pic
{
position
:
relative
;
}
.del-icon
{
position
:
absolute
;
top
:
-10px
;
right
:
-10px
;
z-index
:
1
;
width
:
20px
!important
;
height
:
20px
!important
;
}
</
style
>
src/views/mall/promotion/combination/components/CombinationTableSelect.vue
0 → 100644
View file @
f545dcc6
<
template
>
<Dialog
v-model=
"dialogVisible"
:appendToBody=
"true"
title=
"选择活动"
width=
"70%"
>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
ref=
"queryFormRef"
:inline=
"true"
:model=
"queryParams"
class=
"-mb-15px"
label-width=
"68px"
>
<el-form-item
label=
"活动名称"
prop=
"name"
>
<el-input
v-model=
"queryParams.name"
placeholder=
"请输入活动名称"
clearable
@
keyup
.
enter=
"handleQuery"
class=
"!w-240px"
/>
</el-form-item>
<el-form-item
label=
"活动状态"
prop=
"status"
>
<el-select
v-model=
"queryParams.status"
placeholder=
"请选择活动状态"
clearable
class=
"!w-240px"
>
<el-option
v-for=
"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key=
"dict.value"
:label=
"dict.label"
:value=
"dict.value"
/>
</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-form-item>
</el-form>
<el-table
v-loading=
"loading"
:data=
"list"
show-overflow-tooltip
>
<!-- 1. 多选模式(不能使用type="selection",Element会忽略Header插槽) -->
<el-table-column
width=
"55"
v-if=
"multiple"
>
<template
#
header
>
<el-checkbox
v-model=
"isCheckAll"
:indeterminate=
"isIndeterminate"
@
change=
"handleCheckAll"
/>
</
template
>
<
template
#
default=
"{ row }"
>
<el-checkbox
v-model=
"checkedStatus[row.id]"
@
change=
"(checked: boolean) => handleCheckOne(checked, row, true)"
/>
</
template
>
</el-table-column>
<!-- 2. 单选模式 -->
<el-table-column
label=
"#"
width=
"55"
v-else
>
<
template
#
default=
"{ row }"
>
<el-radio
:value=
"row.id"
v-model=
"selectedActivityId"
@
change=
"handleSingleSelected(row)"
>
<!-- 空格不能省略,是为了让单选框不显示label,如果不指定label不会有选中的效果 -->
</el-radio>
</
template
>
</el-table-column>
<el-table-column
label=
"活动编号"
prop=
"id"
min-width=
"80"
/>
<el-table-column
label=
"活动名称"
prop=
"name"
min-width=
"140"
/>
<el-table-column
label=
"活动时间"
min-width=
"210"
>
<
template
#
default=
"scope"
>
{{
formatDate
(
scope
.
row
.
startTime
,
'YYYY-MM-DD'
)
}}
~
{{
formatDate
(
scope
.
row
.
endTime
,
'YYYY-MM-DD'
)
}}
</
template
>
</el-table-column>
<el-table-column
label=
"商品图片"
prop=
"spuName"
min-width=
"80"
>
<
template
#
default=
"scope"
>
<el-image
:src=
"scope.row.picUrl"
class=
"h-40px w-40px"
:preview-src-list=
"[scope.row.picUrl]"
preview-teleported
/>
</
template
>
</el-table-column>
<el-table-column
label=
"商品标题"
prop=
"spuName"
min-width=
"300"
/>
<el-table-column
label=
"原价"
prop=
"marketPrice"
min-width=
"100"
:formatter=
"fenToYuanFormat"
/>
<el-table-column
label=
"拼团价"
prop=
"seckillPrice"
min-width=
"100"
>
<
template
#
default=
"scope"
>
{{
formatCombinationPrice
(
scope
.
row
.
products
)
}}
</
template
>
</el-table-column>
<el-table-column
label=
"开团组数"
prop=
"groupCount"
min-width=
"100"
/>
<el-table-column
label=
"成团组数"
prop=
"groupSuccessCount"
min-width=
"100"
/>
<el-table-column
label=
"购买次数"
prop=
"recordCount"
min-width=
"100"
/>
<el-table-column
label=
"活动状态"
align=
"center"
prop=
"status"
min-width=
"100"
>
<
template
#
default=
"scope"
>
<dict-tag
:type=
"DICT_TYPE.COMMON_STATUS"
:value=
"scope.row.status"
/>
</
template
>
</el-table-column>
<el-table-column
label=
"创建时间"
align=
"center"
prop=
"createTime"
:formatter=
"dateFormatter"
width=
"180px"
/>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit=
"queryParams.pageSize"
v-model:page=
"queryParams.pageNo"
:total=
"total"
@
pagination=
"getList"
/>
</ContentWrap>
<
template
#
footer
v-if=
"multiple"
>
<el-button
type=
"primary"
@
click=
"handleEmitChange"
>
确 定
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</Dialog>
</template>
<
script
lang=
"ts"
setup
>
import
{
handleTree
}
from
'@/utils/tree'
import
*
as
ProductCategoryApi
from
'@/api/mall/product/category'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
CHANGE_EVENT
}
from
'element-plus'
import
*
as
CombinationActivityApi
from
'@/api/mall/promotion/combination/combinationActivity'
import
{
fenToYuanFormat
}
from
'@/utils/formatter'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
dateFormatter
,
formatDate
}
from
'@/utils/formatTime'
import
{
fenToYuan
}
from
'@/utils'
type
CombinationActivityVO
=
Required
<
CombinationActivityApi
.
CombinationActivityVO
>
/**
* 活动表格选择对话框
* 1. 单选模式:
* 1.1 点击表格左侧的单选框时,结束选择,并关闭对话框
* 1.2 再次打开时,保持选中状态
* 2. 多选模式:
* 2.1 点击表格左侧的多选框时,记录选中的活动
* 2.2 切换分页时,保持活动的选中状态
* 2.3 点击右下角的确定按钮时,结束选择,关闭对话框
* 2.4 再次打开时,保持选中状态
*/
defineOptions
({
name
:
'CombinationTableSelect'
})
defineProps
({
// 多选模式
multiple
:
propTypes
.
bool
.
def
(
false
)
})
// 列表的总页数
const
total
=
ref
(
0
)
// 列表的数据
const
list
=
ref
<
CombinationActivityVO
[]
>
([])
// 列表的加载中
const
loading
=
ref
(
false
)
// 弹窗的是否展示
const
dialogVisible
=
ref
(
false
)
// 查询参数
const
queryParams
=
ref
({
pageNo
:
1
,
pageSize
:
10
,
name
:
null
,
status
:
undefined
})
/** 打开弹窗 */
const
open
=
(
CombinationList
?:
CombinationActivityVO
[])
=>
{
// 重置
checkedActivitys
.
value
=
[]
checkedStatus
.
value
=
{}
isCheckAll
.
value
=
false
isIndeterminate
.
value
=
false
// 处理已选中
if
(
CombinationList
&&
CombinationList
.
length
>
0
)
{
checkedActivitys
.
value
=
[...
CombinationList
]
checkedStatus
.
value
=
Object
.
fromEntries
(
CombinationList
.
map
((
activityVO
)
=>
[
activityVO
.
id
,
true
])
)
}
dialogVisible
.
value
=
true
resetQuery
()
}
// 提供 open 方法,用于打开弹窗
defineExpose
({
open
})
/** 查询列表 */
const
getList
=
async
()
=>
{
loading
.
value
=
true
try
{
const
data
=
await
CombinationActivityApi
.
getCombinationActivityPage
(
queryParams
.
value
)
list
.
value
=
data
.
list
total
.
value
=
data
.
total
// checkbox绑定undefined会有问题,需要给一个bool值
list
.
value
.
forEach
(
(
activityVO
)
=>
(
checkedStatus
.
value
[
activityVO
.
id
]
=
checkedStatus
.
value
[
activityVO
.
id
]
||
false
)
)
// 计算全选框状态
calculateIsCheckAll
()
}
finally
{
loading
.
value
=
false
}
}
/** 搜索按钮操作 */
const
handleQuery
=
()
=>
{
queryParams
.
value
.
pageNo
=
1
getList
()
}
/** 重置按钮操作 */
const
resetQuery
=
()
=>
{
queryParams
.
value
=
{
pageNo
:
1
,
pageSize
:
10
,
name
:
''
,
createTime
:
[]
}
getList
()
}
/**
* 格式化拼团价格
* @param products
*/
const
formatCombinationPrice
=
(
products
)
=>
{
const
combinationPrice
=
Math
.
min
(...
products
.
map
((
item
)
=>
item
.
combinationPrice
))
return
`¥
${
fenToYuan
(
combinationPrice
)}
`
}
// 是否全选
const
isCheckAll
=
ref
(
false
)
// 全选框是否处于中间状态:不是全部选中 && 任意一个选中
const
isIndeterminate
=
ref
(
false
)
// 选中的活动
const
checkedActivitys
=
ref
<
CombinationActivityVO
[]
>
([])
// 选中状态:key为活动ID,value为是否选中
const
checkedStatus
=
ref
<
Record
<
string
,
boolean
>>
({})
// 选中的活动 activityId
const
selectedActivityId
=
ref
()
/** 单选中时触发 */
const
handleSingleSelected
=
(
combinationActivityVO
:
CombinationActivityVO
)
=>
{
emits
(
CHANGE_EVENT
,
combinationActivityVO
)
// 关闭弹窗
dialogVisible
.
value
=
false
// 记住上次选择的ID
selectedActivityId
.
value
=
combinationActivityVO
.
id
}
/** 多选完成 */
const
handleEmitChange
=
()
=>
{
// 关闭弹窗
dialogVisible
.
value
=
false
emits
(
CHANGE_EVENT
,
[...
checkedActivitys
.
value
])
}
/** 确认选择时的触发事件 */
const
emits
=
defineEmits
<
{
change
:
[
CombinationActivityApi
:
CombinationActivityVO
|
CombinationActivityVO
[]
|
any
]
}
>
()
/** 全选/全不选 */
const
handleCheckAll
=
(
checked
:
boolean
)
=>
{
isCheckAll
.
value
=
checked
isIndeterminate
.
value
=
false
list
.
value
.
forEach
((
combinationActivity
)
=>
handleCheckOne
(
checked
,
combinationActivity
,
false
))
}
/**
* 选中一行
* @param checked 是否选中
* @param combinationActivity 活动
* @param isCalcCheckAll 是否计算全选
*/
const
handleCheckOne
=
(
checked
:
boolean
,
combinationActivity
:
CombinationActivityVO
,
isCalcCheckAll
:
boolean
)
=>
{
if
(
checked
)
{
checkedActivitys
.
value
.
push
(
combinationActivity
)
checkedStatus
.
value
[
combinationActivity
.
id
]
=
true
}
else
{
const
index
=
findCheckedIndex
(
combinationActivity
)
if
(
index
>
-
1
)
{
checkedActivitys
.
value
.
splice
(
index
,
1
)
checkedStatus
.
value
[
combinationActivity
.
id
]
=
false
isCheckAll
.
value
=
false
}
}
// 计算全选框状态
if
(
isCalcCheckAll
)
{
calculateIsCheckAll
()
}
}
// 查找活动在已选中活动列表中的索引
const
findCheckedIndex
=
(
activityVO
:
CombinationActivityVO
)
=>
checkedActivitys
.
value
.
findIndex
((
item
)
=>
item
.
id
===
activityVO
.
id
)
// 计算全选框状态
const
calculateIsCheckAll
=
()
=>
{
isCheckAll
.
value
=
list
.
value
.
every
((
activityVO
)
=>
checkedStatus
.
value
[
activityVO
.
id
])
// 计算中间状态:不是全部选中 && 任意一个选中
isIndeterminate
.
value
=
!
isCheckAll
.
value
&&
list
.
value
.
some
((
activityVO
)
=>
checkedStatus
.
value
[
activityVO
.
id
])
}
// 分类列表
const
categoryList
=
ref
()
// 分类树
const
categoryTreeList
=
ref
()
/** 初始化 **/
onMounted
(
async
()
=>
{
await
getList
()
// 获得分类树
categoryList
.
value
=
await
ProductCategoryApi
.
getCategoryList
({})
categoryTreeList
.
value
=
handleTree
(
categoryList
.
value
,
'id'
,
'parentId'
)
})
</
script
>
src/views/mall/promotion/components/SpuAndSkuList.vue
View file @
f545dcc6
...
@@ -29,15 +29,14 @@
...
@@ -29,15 +29,14 @@
</el-table-column>
</el-table-column>
<el-table-column
align=
"center"
label=
"销量"
min-width=
"90"
prop=
"salesCount"
/>
<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=
"90"
prop=
"stock"
/>
<el-table-column
v-if=
"spuData.length > 1 && isDelete"
align=
"center"
label=
"操作"
min-width=
"90"
>
<el-table-column
<
template
#
default=
"scope"
>
v-if=
"spuData.length > 1 && isDelete"
<el-button
align=
"center"
type=
"primary"
label=
"操作"
link
min-width=
"90"
@
click=
"deleteSpu(scope.row.id)"
>
>
删除
<
template
#
default=
"scope"
>
</el-button>
<
el-button
type=
"primary"
link
@
click=
"deleteSpu(scope.row.id)"
>
删除
<
/el-button>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
</el-table>
</el-table>
...
@@ -57,7 +56,7 @@ const props = defineProps<{
...
@@ -57,7 +56,7 @@ const props = defineProps<{
spuList
:
T
[]
spuList
:
T
[]
ruleConfig
:
RuleConfig
[]
ruleConfig
:
RuleConfig
[]
spuPropertyListP
:
SpuProperty
<
T
>
[]
spuPropertyListP
:
SpuProperty
<
T
>
[]
isDelete
?:
boolean
//
spu是否可以多选
isDelete
?:
boolean
//
SPU 是否可删除;TODO deletable 换成这个名字好点。
}
>
()
}
>
()
const
spuData
=
ref
<
Spu
[]
>
([])
// spu 详情数据列表
const
spuData
=
ref
<
Spu
[]
>
([])
// spu 详情数据列表
...
@@ -96,12 +95,12 @@ const emits = defineEmits<{
...
@@ -96,12 +95,12 @@ const emits = defineEmits<{
(
e
:
'delete'
,
spuId
:
number
):
void
(
e
:
'delete'
,
spuId
:
number
):
void
}
>
()
}
>
()
/** 多选时可以删除
spu
**/
/** 多选时可以删除
SPU
**/
const
deleteSpu
=
async
(
spuId
:
number
)
=>
{
const
deleteSpu
=
async
(
spuId
:
number
)
=>
{
await
message
.
confirm
(
'是否删除商品编号为'
+
spuId
+
'的数据?'
)
await
message
.
confirm
(
'是否删除商品编号为'
+
spuId
+
'的数据?'
)
le
t
index
=
spuData
.
value
.
findIndex
((
item
)
=>
item
.
id
==
spuId
)
cons
t
index
=
spuData
.
value
.
findIndex
((
item
)
=>
item
.
id
==
spuId
)
spuData
.
value
.
splice
(
index
,
1
);
spuData
.
value
.
splice
(
index
,
1
)
emits
(
'delete'
,
spuId
)
emits
(
'delete'
,
spuId
)
}
}
/**
/**
...
...
src/views/mall/promotion/coupon/components/CouponSelect.vue
View file @
f545dcc6
...
@@ -33,32 +33,6 @@
...
@@ -33,32 +33,6 @@
/>
/>
</el-select>
</el-select>
</el-form-item>
</el-form-item>
<el-form-item
label=
"优惠券状态"
prop=
"status"
>
<el-select
v-model=
"queryParams.status"
class=
"!w-240px"
clearable
placeholder=
"请选择优惠券状态"
>
<el-option
v-for=
"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key=
"dict.value"
:label=
"dict.label"
:value=
"dict.value"
/>
</el-select>
</el-form-item>
<el-form-item
label=
"创建时间"
prop=
"createTime"
>
<el-date-picker
v-model=
"queryParams.createTime"
:default-time=
"[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class=
"!w-240px"
end-placeholder=
"结束日期"
start-placeholder=
"开始日期"
type=
"daterange"
value-format=
"YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-form-item>
<el-button
@
click=
"handleQuery"
>
<el-button
@
click=
"handleQuery"
>
<Icon
class=
"mr-5px"
icon=
"ep:search"
/>
<Icon
class=
"mr-5px"
icon=
"ep:search"
/>
...
@@ -118,13 +92,6 @@
...
@@ -118,13 +92,6 @@
<dict-tag
:type=
"DICT_TYPE.COMMON_STATUS"
:value=
"scope.row.status"
/>
<dict-tag
:type=
"DICT_TYPE.COMMON_STATUS"
:value=
"scope.row.status"
/>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"创建时间"
prop=
"createTime"
width=
"180"
/>
</el-table>
</el-table>
<!-- 分页 -->
<!-- 分页 -->
<Pagination
<Pagination
...
@@ -148,16 +115,17 @@ import {
...
@@ -148,16 +115,17 @@ import {
takeLimitCountFormat
,
takeLimitCountFormat
,
validityTypeFormat
validityTypeFormat
}
from
'@/views/mall/promotion/coupon/formatter'
}
from
'@/views/mall/promotion/coupon/formatter'
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
*
as
CouponTemplateApi
from
'@/api/mall/promotion/coupon/couponTemplate'
import
*
as
CouponTemplateApi
from
'@/api/mall/promotion/coupon/couponTemplate'
defineOptions
({
name
:
'CouponSelect'
})
defineOptions
({
name
:
'CouponSelect'
})
defineProps
<
{
const
props
=
defineProps
<
{
multipleSelection
:
CouponTemplateApi
.
CouponTemplateVO
[]
multipleSelection
?:
CouponTemplateApi
.
CouponTemplateVO
[]
takeType
:
number
// 领取方式
}
>
()
}
>
()
const
emit
=
defineEmits
<
{
const
emit
=
defineEmits
<
{
(
e
:
'update:multipleSelection'
,
v
:
CouponTemplateApi
.
CouponTemplateVO
[])
(
e
:
'update:multipleSelection'
,
v
:
CouponTemplateApi
.
CouponTemplateVO
[]):
void
(
e
:
'change'
,
v
:
CouponTemplateApi
.
CouponTemplateVO
[]):
void
}
>
()
}
>
()
const
dialogVisible
=
ref
(
false
)
// 弹窗的是否展示
const
dialogVisible
=
ref
(
false
)
// 弹窗的是否展示
const
dialogTitle
=
ref
(
'选择优惠卷'
)
// 弹窗的标题
const
dialogTitle
=
ref
(
'选择优惠卷'
)
// 弹窗的标题
...
@@ -169,18 +137,18 @@ const queryParams = reactive({
...
@@ -169,18 +137,18 @@ const queryParams = reactive({
pageNo
:
1
,
pageNo
:
1
,
pageSize
:
10
,
pageSize
:
10
,
name
:
null
,
name
:
null
,
status
:
null
,
discountType
:
null
,
discountType
:
null
,
type
:
null
,
canTakeTypes
:
null
createTime
:
[]
})
})
const
queryFormRef
=
ref
()
// 搜索的表单
const
queryFormRef
=
ref
()
// 搜索的表单
const
selectedCouponList
=
ref
<
CouponTemplateApi
.
CouponTemplateVO
[]
>
([])
// 选择的数据
/** 查询列表 */
/** 查询列表 */
const
getList
=
async
()
=>
{
const
getList
=
async
()
=>
{
loading
.
value
=
true
loading
.
value
=
true
try
{
try
{
// 执行查询
// 执行查询
queryParams
.
canTakeTypes
=
[
props
.
takeType
]
as
any
const
data
=
await
CouponTemplateApi
.
getCouponTemplatePage
(
queryParams
)
const
data
=
await
CouponTemplateApi
.
getCouponTemplatePage
(
queryParams
)
list
.
value
=
data
.
list
list
.
value
=
data
.
list
total
.
value
=
data
.
total
total
.
value
=
data
.
total
...
@@ -209,11 +177,15 @@ const open = async () => {
...
@@ -209,11 +177,15 @@ const open = async () => {
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
const
handleSelectionChange
=
(
val
:
CouponTemplateApi
.
CouponTemplateVO
[])
=>
{
const
handleSelectionChange
=
(
val
:
CouponTemplateApi
.
CouponTemplateVO
[])
=>
{
if
(
props
.
multipleSelection
)
{
emit
(
'update:multipleSelection'
,
val
)
emit
(
'update:multipleSelection'
,
val
)
return
}
selectedCouponList
.
value
=
val
}
}
const
submitForm
=
()
=>
{
const
submitForm
=
()
=>
{
dialogVisible
.
value
=
false
dialogVisible
.
value
=
false
emit
(
'change'
,
selectedCouponList
.
value
)
}
}
// TODO @puhui999:提前 todo,先不用改;未来单独成组件,其它模块可以服用;例如说,满减送,可以选择优惠劵;
</
script
>
</
script
>
src/views/mall/promotion/discountActivity/DiscountActivityForm.vue
View file @
f545dcc6
...
@@ -49,7 +49,7 @@ import { cloneDeep } from 'lodash-es'
...
@@ -49,7 +49,7 @@ import { cloneDeep } from 'lodash-es'
import
*
as
DiscountActivityApi
from
'@/api/mall/promotion/discount/discountActivity'
import
*
as
DiscountActivityApi
from
'@/api/mall/promotion/discount/discountActivity'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
*
as
ProductSpuApi
from
'@/api/mall/product/spu'
import
{
getPropertyList
,
RuleConfig
}
from
'@/views/mall/product/spu/components'
import
{
getPropertyList
,
RuleConfig
}
from
'@/views/mall/product/spu/components'
import
{
formatToFraction
}
from
"@/utils"
;
import
{
formatToFraction
}
from
'@/utils'
defineOptions
({
name
:
'PromotionDiscountActivityForm'
})
defineOptions
({
name
:
'PromotionDiscountActivityForm'
})
...
@@ -68,7 +68,7 @@ const spuAndSkuListRef = ref() // sku 限时折扣 配置组件Ref
...
@@ -68,7 +68,7 @@ const spuAndSkuListRef = ref() // sku 限时折扣 配置组件Ref
const
ruleConfig
:
RuleConfig
[]
=
[]
const
ruleConfig
:
RuleConfig
[]
=
[]
const
spuList
=
ref
<
DiscountActivityApi
.
SpuExtension
[]
>
([])
// 选择的 spu
const
spuList
=
ref
<
DiscountActivityApi
.
SpuExtension
[]
>
([])
// 选择的 spu
const
spuPropertyList
=
ref
<
SpuProperty
<
DiscountActivityApi
.
SpuExtension
>
[]
>
([])
const
spuPropertyList
=
ref
<
SpuProperty
<
DiscountActivityApi
.
SpuExtension
>
[]
>
([])
const
spuIds
=
ref
<
number
[]
>
([])
;
const
spuIds
=
ref
<
number
[]
>
([])
const
selectSpu
=
(
spuId
:
number
,
skuIds
:
number
[])
=>
{
const
selectSpu
=
(
spuId
:
number
,
skuIds
:
number
[])
=>
{
getSpuDetails
(
spuId
,
skuIds
)
getSpuDetails
(
spuId
,
skuIds
)
}
}
...
@@ -81,12 +81,12 @@ const getSpuDetails = async (
...
@@ -81,12 +81,12 @@ const getSpuDetails = async (
products
?:
DiscountActivityApi
.
DiscountProductVO
[],
products
?:
DiscountActivityApi
.
DiscountProductVO
[],
type
?:
string
type
?:
string
)
=>
{
)
=>
{
//
如果已经包含spu
则跳过
//
如果已经包含 SPU
则跳过
if
(
spuIds
.
value
.
includes
(
spuId
))
{
if
(
spuIds
.
value
.
includes
(
spuId
))
{
if
(
type
!==
"load"
)
{
if
(
type
!==
'load'
)
{
message
.
error
(
"数据重复选择!"
)
message
.
error
(
'数据重复选择!'
)
}
}
return
;
return
}
}
spuIds
.
value
.
push
(
spuId
)
spuIds
.
value
.
push
(
spuId
)
const
res
=
(
await
ProductSpuApi
.
getSpuDetailList
([
spuId
]))
as
DiscountActivityApi
.
SpuExtension
[]
const
res
=
(
await
ProductSpuApi
.
getSpuDetailList
([
spuId
]))
as
DiscountActivityApi
.
SpuExtension
[]
...
@@ -143,7 +143,12 @@ const open = async (type: string, id?: number) => {
...
@@ -143,7 +143,12 @@ const open = async (type: string, id?: number) => {
))
as
DiscountActivityApi
.
DiscountActivityVO
))
as
DiscountActivityApi
.
DiscountActivityVO
for
(
let
productsKey
in
data
.
products
)
{
for
(
let
productsKey
in
data
.
products
)
{
const
supId
=
data
.
products
[
productsKey
].
spuId
const
supId
=
data
.
products
[
productsKey
].
spuId
await
getSpuDetails
(
supId
!
,
data
.
products
?.
map
((
sku
)
=>
sku
.
skuId
),
data
.
products
,
"load"
)
await
getSpuDetails
(
supId
!
,
data
.
products
?.
map
((
sku
)
=>
sku
.
skuId
),
data
.
products
,
'load'
)
}
}
formRef
.
value
.
setValues
(
data
)
formRef
.
value
.
setValues
(
data
)
}
finally
{
}
finally
{
...
@@ -164,21 +169,23 @@ const submitForm = async () => {
...
@@ -164,21 +169,23 @@ const submitForm = async () => {
formLoading
.
value
=
true
formLoading
.
value
=
true
try
{
try
{
const
data
=
formRef
.
value
.
formModel
as
DiscountActivityApi
.
DiscountActivityVO
const
data
=
formRef
.
value
.
formModel
as
DiscountActivityApi
.
DiscountActivityVO
// 获取
折扣商品配置
// 获取折扣商品配置
const
products
=
cloneDeep
(
spuAndSkuListRef
.
value
.
getSkuConfigs
(
'productConfig'
))
const
products
=
cloneDeep
(
spuAndSkuListRef
.
value
.
getSkuConfigs
(
'productConfig'
))
let
timp
=
false
;
// 校验优惠金额、折扣百分比,是否正确
// TODO @puhui999:这个交互,可以参考下 youzan 的
let
discountInvalid
=
false
products
.
forEach
((
item
:
DiscountActivityApi
.
DiscountProductVO
)
=>
{
products
.
forEach
((
item
:
DiscountActivityApi
.
DiscountProductVO
)
=>
{
if
(
item
.
discountPrice
!=
null
&&
item
.
discountPrice
>
0
)
{
if
(
item
.
discountPrice
!=
null
&&
item
.
discountPrice
>
0
)
{
item
.
discountType
=
1
item
.
discountType
=
1
}
else
if
(
item
.
discountPercent
!=
null
&&
item
.
discountPercent
>
0
)
{
}
else
if
(
item
.
discountPercent
!=
null
&&
item
.
discountPercent
>
0
)
{
item
.
discountType
=
2
item
.
discountType
=
2
}
else
{
}
else
{
timp
=
true
discountInvalid
=
true
}
}
})
})
if
(
timp
)
{
if
(
discountInvalid
)
{
message
.
error
(
"优惠金额和折扣百分比需要填写一个"
);
message
.
error
(
'优惠金额和折扣百分比需要填写一个'
)
return
;
return
}
}
data
.
products
=
products
data
.
products
=
products
// 真正提交
// 真正提交
...
@@ -207,10 +214,16 @@ const resetForm = async () => {
...
@@ -207,10 +214,16 @@ const resetForm = async () => {
}
}
/**
/**
* 删除
spu
* 删除
SPU
*/
*/
const
deleteSpu
=
(
spuId
:
number
)
=>
{
const
deleteSpu
=
(
spuId
:
number
)
=>
{
spuIds
.
value
.
splice
(
spuIds
.
value
.
findIndex
((
item
)
=>
item
==
spuId
),
1
)
spuIds
.
value
.
splice
(
spuPropertyList
.
value
.
splice
(
spuPropertyList
.
value
.
findIndex
((
item
)
=>
item
.
spuId
==
spuId
),
1
)
spuIds
.
value
.
findIndex
((
item
)
=>
item
==
spuId
),
1
)
spuPropertyList
.
value
.
splice
(
spuPropertyList
.
value
.
findIndex
((
item
)
=>
item
.
spuId
==
spuId
),
1
)
}
}
</
script
>
</
script
>
src/views/mall/promotion/kefu/components/KeFuMessageList.vue
View file @
f545dcc6
...
@@ -86,7 +86,7 @@
...
@@ -86,7 +86,7 @@
<OrderItem
<OrderItem
v-if=
"KeFuMessageContentTypeEnum.ORDER === item.contentType"
v-if=
"KeFuMessageContentTypeEnum.ORDER === item.contentType"
:message=
"item"
:message=
"item"
class=
"max-w-
7
0%"
class=
"max-w-
10
0%"
/>
/>
</MessageItem>
</MessageItem>
</div>
</div>
...
@@ -423,9 +423,9 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
...
@@ -423,9 +423,9 @@ const showTime = computed(() => (item: KeFuMessageRespVO, index: number) => {
//
消息气泡
//
消息气泡
.kefu-message
{
.kefu-message
{
color
:
#
A9A9A
9
;
color
:
#
a9a9a
9
;
border-radius
:
5px
;
border-radius
:
5px
;
box-shadow
:
3px
3px
5px
rgba
(
220
,
220
,
220
,
0.1
);
box-shadow
:
3px
3px
5px
rgba
(
220
,
220
,
220
,
0.1
);
padding
:
5px
10px
;
padding
:
5px
10px
;
width
:
auto
;
width
:
auto
;
max-width
:
50%
;
max-width
:
50%
;
...
...
src/views/mall/promotion/kefu/components/tools/emoji.ts
View file @
f545dcc6
...
@@ -66,7 +66,7 @@ export const useEmoji = () => {
...
@@ -66,7 +66,7 @@ export const useEmoji = () => {
)
)
for
(
const
path
in
pathList
)
{
for
(
const
path
in
pathList
)
{
const
imageModule
:
any
=
await
pathList
[
path
]()
const
imageModule
:
any
=
await
pathList
[
path
]()
emojiPathList
.
value
.
push
(
imageModule
.
default
)
emojiPathList
.
value
.
push
(
{
path
:
path
,
src
:
imageModule
.
default
}
)
}
}
}
}
...
@@ -116,7 +116,10 @@ export const useEmoji = () => {
...
@@ -116,7 +116,10 @@ export const useEmoji = () => {
function
getEmojiFileByName
(
name
:
string
)
{
function
getEmojiFileByName
(
name
:
string
)
{
for
(
const
emoji
of
emojiList
)
{
for
(
const
emoji
of
emojiList
)
{
if
(
emoji
.
name
===
name
)
{
if
(
emoji
.
name
===
name
)
{
return
emojiPathList
.
value
.
find
((
item
:
string
)
=>
item
.
indexOf
(
emoji
.
file
)
>
-
1
)
const
emojiPath
=
emojiPathList
.
value
.
find
(
(
item
:
{
path
:
string
;
src
:
string
})
=>
item
.
path
.
indexOf
(
emoji
.
file
)
>
-
1
)
return
emojiPath
?
emojiPath
.
src
:
undefined
}
}
}
}
return
false
return
false
...
...
src/views/mall/promotion/rewardActivity/RewardForm.vue
View file @
f545dcc6
<
template
>
<
template
>
<Dialog
:title=
"dialogTitle"
v-model=
"dialogVisible
"
>
<Dialog
v-model=
"dialogVisible"
:title=
"dialogTitle"
width=
"65%
"
>
<el-form
<el-form
ref=
"formRef"
ref=
"formRef"
v-loading=
"formLoading"
:model=
"formData"
:model=
"formData"
:rules=
"formRules"
:rules=
"formRules"
label-width=
"80px"
label-width=
"80px"
v-loading=
"formLoading"
>
>
<el-form-item
label=
"活动名称"
prop=
"name"
>
<el-form-item
label=
"活动名称"
prop=
"name"
>
<el-input
v-model=
"formData.name"
placeholder=
"请输入活动名称"
/>
<el-input
v-model=
"formData.name"
placeholder=
"请输入活动名称"
/>
...
@@ -13,10 +13,11 @@
...
@@ -13,10 +13,11 @@
<el-form-item
label=
"活动时间"
prop=
"startAndEndTime"
>
<el-form-item
label=
"活动时间"
prop=
"startAndEndTime"
>
<el-date-picker
<el-date-picker
v-model=
"formData.startAndEndTime"
v-model=
"formData.startAndEndTime"
type=
"datetimerange"
range-separator=
"-"
:start-placeholder=
"t('common.startTimeText')"
:end-placeholder=
"t('common.endTimeText')"
:end-placeholder=
"t('common.endTimeText')"
:start-placeholder=
"t('common.startTimeText')"
range-separator=
"-"
type=
"datetimerange"
value-format=
"x"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
label=
"条件类型"
prop=
"conditionType"
>
<el-form-item
label=
"条件类型"
prop=
"conditionType"
>
...
@@ -24,187 +25,83 @@
...
@@ -24,187 +25,83 @@
<el-radio
<el-radio
v-for=
"dict in getIntDictOptions(DICT_TYPE.PROMOTION_CONDITION_TYPE)"
v-for=
"dict in getIntDictOptions(DICT_TYPE.PROMOTION_CONDITION_TYPE)"
:key=
"dict.value"
:key=
"dict.value"
:
value
=
"dict.value"
:
label
=
"dict.value"
>
>
{{
dict
.
label
}}
{{
dict
.
label
}}
</el-radio>
</el-radio>
</el-radio-group>
</el-radio-group>
</el-form-item>
</el-form-item>
<el-form-item
label=
"优惠设置"
>
<el-form-item
label=
"优惠设置"
>
<template
v-for=
"(item, index) in formData.rules"
:key=
"index"
>
<RewardRule
ref=
"rewardRuleRef"
v-model=
"formData"
/>
<el-row
type=
"flex"
>
<el-col
:span=
"24"
style=
"display: flex; font-weight: bold"
>
活动层级
{{
index
+
1
}}
<el-button
link
type=
"danger"
style=
"margin-left: auto"
v-if=
"index != 0"
@
click=
"deleteActivityRule(index)"
>
删除
</el-button>
</el-col>
<el-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>
<el-form-item
label=
"优惠内容:"
label-width=
"100px"
style=
"padding-left: 50px"
>
<el-form-item
label=
"活动范围"
prop=
"productScope"
>
<el-checkbox-group
v-model=
"activityRules[index]"
style=
"width: 100%"
>
<el-col
:span=
"24"
>
<el-checkbox
label=
"订单金额优惠"
value=
"订单金额优惠"
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=
"包邮"
value=
"包邮"
name=
"type"
/>
</el-col>
<el-col
:span=
"24"
>
<el-checkbox
label=
"送积分"
value=
"送积分"
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=
"送优惠券"
value=
"送优惠券"
name=
"type"
/>
</el-col>
</el-checkbox-group>
</el-form-item>
</el-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-group
v-model=
"formData.productScope"
>
<el-radio
<el-radio
v-for=
"dict in getIntDictOptions(DICT_TYPE.PROMOTION_PRODUCT_SCOPE)"
v-for=
"dict in getIntDictOptions(DICT_TYPE.PROMOTION_PRODUCT_SCOPE)"
:key=
"dict.value"
:key=
"dict.value"
:
value
=
"dict.value"
:
label
=
"dict.value"
>
>
{{
dict
.
label
}}
{{
dict
.
label
}}
</el-radio>
</el-radio>
</el-radio-group>
</el-radio-group>
</el-form-item>
</el-form-item>
<!-- TODO:活动商品的开发,可以参考优惠劵的,已经搞好啦; -->
<el-form-item
<el-form-item
v-if=
"formData.productScope === PromotionProductScopeEnum.SPU.scope"
v-if=
"formData.productScope === PromotionProductScopeEnum.SPU.scope"
prop=
"productSpuIds"
prop=
"productSpuIds"
>
>
<el-select
<SpuShowcase
v-model=
"formData.productSpuIds"
/>
v-model=
"formData.productSpuIds"
</el-form-item>
placeholder=
"请选择活动商品"
<el-form-item
clearable
v-if=
"formData.productScope === PromotionProductScopeEnum.CATEGORY.scope"
size=
"small"
label=
"分类"
multiple
prop=
"productCategoryIds"
filterable
style=
"width: 400px"
>
>
<el-option
v-for=
"item in productSpus"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
>
<ProductCategorySelect
v-model=
"formData.productCategoryIds"
/>
<span
style=
"float: left"
>
{{ item.name }}
</span>
<span
style=
"float: right; font-size: 13px; color: #8492a6"
>
¥{{ (item.price / 100.0).toFixed(2) }}
</span>
</el-option>
</el-select>
</el-form-item>
</el-form-item>
<el-form-item
label=
"备注"
prop=
"remark"
>
<el-form-item
label=
"备注"
prop=
"remark"
>
<el-input
v-model=
"formData.remark"
placeholder=
"请输入备注"
/>
<el-input
v-model=
"formData.remark"
placeholder=
"请输入备注"
/>
</el-form-item>
</el-form-item>
</el-form>
</el-form>
<template
#
footer
>
<template
#
footer
>
<el-button
@
click=
"submitForm"
type=
"primary"
:disabled=
"formLoading
"
>
确 定
</el-button>
<el-button
:disabled=
"formLoading"
type=
"primary"
@
click=
"submitForm
"
>
确 定
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</
template
>
</Dialog>
</Dialog>
</template>
</template>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
getSpuSimpleList
}
from
'@/api/mall/product/spu'
import
RewardRule
from
'./components/RewardRule.vue'
import
SpuShowcase
from
'@/views/mall/product/spu/components/SpuShowcase.vue'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
*
as
RewardActivityApi
from
'@/api/mall/promotion/reward/rewardActivity'
import
*
as
RewardActivityApi
from
'@/api/mall/promotion/reward/rewardActivity'
import
{
PromotionConditionTypeEnum
,
PromotionProductScopeEnum
}
from
'@/utils/constants'
import
{
PromotionConditionTypeEnum
,
PromotionProductScopeEnum
}
from
'@/utils/constants'
import
ProductCategorySelect
from
'@/views/mall/product/category/components/ProductCategorySelect.vue'
import
{
cloneDeep
}
from
'lodash-es'
import
{
fenToYuan
,
yuanToFen
}
from
'@/utils'
/** 初始化 **/
onMounted
(()
=>
{
getSpuSimpleList
().
then
((
response
)
=>
{
productSpus
.
value
=
response
})
})
defineOptions
({
name
:
'ProductBrandForm'
})
defineOptions
({
name
:
'ProductBrandForm'
})
const
{
t
}
=
useI18n
()
// 国际化
const
{
t
}
=
useI18n
()
// 国际化
const
message
=
useMessage
()
// 消息弹窗
const
message
=
useMessage
()
// 消息弹窗
const
productSpus
=
ref
<
any
[]
>
([])
// 商品数据
const
dialogVisible
=
ref
(
false
)
// 弹窗的是否展示
const
dialogVisible
=
ref
(
false
)
// 弹窗的是否展示
const
dialogTitle
=
ref
(
''
)
// 弹窗的标题
const
dialogTitle
=
ref
(
''
)
// 弹窗的标题
const
formLoading
=
ref
(
false
)
// 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const
formLoading
=
ref
(
false
)
// 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const
formType
=
ref
(
''
)
// 表单的类型:create - 新增;update - 修改
const
formType
=
ref
(
''
)
// 表单的类型:create - 新增;update - 修改
const
formData
=
ref
({
const
formData
=
ref
<
RewardActivityApi
.
RewardActivityVO
>
({
id
:
undefined
,
name
:
undefined
,
startAndEndTime
:
undefined
,
startTime
:
undefined
,
endTime
:
undefined
,
conditionType
:
PromotionConditionTypeEnum
.
PRICE
.
type
,
conditionType
:
PromotionConditionTypeEnum
.
PRICE
.
type
,
remark
:
undefined
,
productScope
:
PromotionProductScopeEnum
.
ALL
.
scope
,
productScope
:
PromotionProductScopeEnum
.
ALL
.
scope
,
productSpuIds
:
undefined
,
rules
:
[]
rules
:
[
}
as
RewardActivityApi
.
RewardActivityVO
)
{
limit
:
undefined
,
discountPrice
:
undefined
,
freeDelivery
:
undefined
,
point
:
undefined
,
couponIds
:
[],
couponCounts
:
[]
}
]
})
const
activityRules
=
reactive
([])
// 优惠设置。每个元素都是一个 [],放“包邮”、“送积分”、“订单金额优惠”
const
formRules
=
reactive
({
const
formRules
=
reactive
({
name
:
[{
required
:
true
,
message
:
'活动名称不能为空'
,
trigger
:
'blur'
}],
name
:
[{
required
:
true
,
message
:
'活动名称不能为空'
,
trigger
:
'blur'
}],
startAndEndTime
:
[{
required
:
true
,
message
:
'活动时间不能为空'
,
trigger
:
'blur'
}],
startAndEndTime
:
[{
required
:
true
,
message
:
'活动时间不能为空'
,
trigger
:
'blur'
}],
conditionType
:
[{
required
:
true
,
message
:
'条件类型不能为空'
,
trigger
:
'change'
}],
conditionType
:
[{
required
:
true
,
message
:
'条件类型不能为空'
,
trigger
:
'change'
}],
productScope
:
[{
required
:
true
,
message
:
'商品范围不能为空'
,
trigger
:
'blur'
}],
productScope
:
[{
required
:
true
,
message
:
'商品范围不能为空'
,
trigger
:
'blur'
}],
productSpuIds
:
[{
required
:
true
,
message
:
'商品范围不能为空'
,
trigger
:
'blur'
}]
productSpuIds
:
[{
required
:
true
,
message
:
'商品不能为空'
,
trigger
:
'blur'
}],
productCategoryIds
:
[{
required
:
true
,
message
:
'商品分类不能为空'
,
trigger
:
'blur'
}]
})
})
const
formRef
=
ref
([])
// 表单 Ref
const
formRef
=
ref
()
// 表单 Ref
const
rewardRuleRef
=
ref
<
InstanceType
<
typeof
RewardRule
>>
()
// 活动规则 Ref
/** 打开弹窗 */
/** 打开弹窗 */
const
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
const
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
...
@@ -216,24 +113,16 @@ const open = async (type: string, id?: number) => {
...
@@ -216,24 +113,16 @@ const open = async (type: string, id?: number) => {
if
(
id
)
{
if
(
id
)
{
formLoading
.
value
=
true
formLoading
.
value
=
true
try
{
try
{
let
data
=
await
RewardActivityApi
.
getReward
(
id
)
const
data
=
await
RewardActivityApi
.
getReward
(
id
)
data
.
startAndEndTime
=
[
new
Date
(
data
.
startTime
),
new
Date
(
data
.
endTime
)]
// 转区段时间
activityRules
.
splice
(
0
,
activityRules
.
length
)
data
.
startAndEndTime
=
[
data
.
startTime
,
data
.
endTime
]
data
.
rules
.
forEach
((
item
)
=>
{
// 规则分转元
// TODO 是不是不用 reactive,直接 [] 就可以了?
data
.
rules
?.
forEach
((
item
:
any
)
=>
{
let
array
:
string
[]
=
reactive
([])
item
.
discountPrice
=
fenToYuan
(
item
.
discountPrice
||
0
)
if
(
item
.
freeDelivery
)
{
array
.
push
(
'包邮'
)
}
if
(
item
.
point
)
{
array
.
push
(
'送积分'
)
}
if
(
item
.
discountPrice
)
{
array
.
push
(
'订单金额优惠'
)
}
activityRules
.
push
(
array
)
})
})
formData
.
value
=
data
formData
.
value
=
data
// 获得商品范围
await
getProductScope
()
}
finally
{
}
finally
{
formLoading
.
value
=
false
formLoading
.
value
=
false
}
}
...
@@ -245,26 +134,26 @@ defineExpose({ open }) // 提供 open 方法,用于打开弹窗
...
@@ -245,26 +134,26 @@ defineExpose({ open }) // 提供 open 方法,用于打开弹窗
const
emit
=
defineEmits
([
'success'
])
// 定义 success 事件,用于操作成功后的回调
const
emit
=
defineEmits
([
'success'
])
// 定义 success 事件,用于操作成功后的回调
const
submitForm
=
async
()
=>
{
const
submitForm
=
async
()
=>
{
// 校验表单
// 校验表单
if
(
!
formRef
)
return
if
(
!
formRef
.
value
)
return
const
valid
=
await
formRef
.
value
.
validate
()
const
valid
=
await
formRef
.
value
.
validate
()
if
(
!
valid
)
return
if
(
!
valid
)
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
formLoading
.
value
=
true
try
{
try
{
const
data
=
formData
.
value
as
RewardActivityApi
.
DiscountActivityVO
// 设置活动规则优惠券
rewardRuleRef
.
value
?.
setRuleCoupon
()
const
data
=
cloneDeep
(
formData
.
value
)
// 时间段转换
data
.
startTime
=
data
.
startAndEndTime
!
[
0
]
data
.
endTime
=
data
.
startAndEndTime
!
[
1
]
delete
data
.
startAndEndTime
// 规则元转分
data
.
rules
.
forEach
((
item
)
=>
{
item
.
discountPrice
=
yuanToFen
(
item
.
discountPrice
||
0
)
})
// 设置商品范围
setProductScopeValues
(
data
)
if
(
formType
.
value
===
'create'
)
{
if
(
formType
.
value
===
'create'
)
{
await
RewardActivityApi
.
createRewardActivity
(
data
)
await
RewardActivityApi
.
createRewardActivity
(
data
)
message
.
success
(
t
(
'common.createSuccess'
))
message
.
success
(
t
(
'common.createSuccess'
))
...
@@ -280,51 +169,50 @@ const submitForm = async () => {
...
@@ -280,51 +169,50 @@ 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
=
()
=>
{
const
resetForm
=
()
=>
{
formData
.
value
=
{
formData
.
value
=
{
id
:
undefined
,
name
:
undefined
,
startAndEndTime
:
undefined
,
startTime
:
undefined
,
endTime
:
undefined
,
conditionType
:
PromotionConditionTypeEnum
.
PRICE
.
type
,
conditionType
:
PromotionConditionTypeEnum
.
PRICE
.
type
,
remark
:
undefined
,
productScope
:
PromotionProductScopeEnum
.
ALL
.
scope
,
productScope
:
PromotionProductScopeEnum
.
ALL
.
scope
,
productSpuIds
:
undefined
,
rules
:
[]
rules
:
[
}
as
RewardActivityApi
.
RewardActivityVO
{
}
limit
:
undefined
,
discountPrice
:
undefined
,
/** 获得商品范围 */
freeDelivery
:
undefined
,
const
getProductScope
=
async
()
=>
{
point
:
undefined
,
switch
(
formData
.
value
.
productScope
)
{
couponIds
:
[],
case
PromotionProductScopeEnum
.
SPU
.
scope
:
couponCounts
:
[]
// 设置商品编号
formData
.
value
.
productSpuIds
=
formData
.
value
.
productScopeValues
break
case
PromotionProductScopeEnum
.
CATEGORY
.
scope
:
await
nextTick
()
let
productCategoryIds
=
formData
.
value
.
productScopeValues
as
any
if
(
Array
.
isArray
(
productCategoryIds
)
&&
productCategoryIds
.
length
>
0
)
{
// 单选时使用数组不能反显
productCategoryIds
=
productCategoryIds
[
0
]
}
}
]
// 设置品类编号
formData
.
value
.
productCategoryIds
=
productCategoryIds
break
default
:
break
}
}
/** 设置商品范围 */
function
setProductScopeValues
(
data
:
any
)
{
switch
(
formData
.
value
.
productScope
)
{
case
PromotionProductScopeEnum
.
SPU
.
scope
:
data
.
productScopeValues
=
formData
.
value
.
productSpuIds
break
case
PromotionProductScopeEnum
.
CATEGORY
.
scope
:
data
.
productScopeValues
=
Array
.
isArray
(
formData
.
value
.
productCategoryIds
)
?
formData
.
value
.
productCategoryIds
:
[
formData
.
value
.
productCategoryIds
]
break
default
:
break
}
}
activityRules
.
splice
(
0
,
activityRules
.
length
)
activityRules
.
push
(
reactive
([]))
// 解决下有时刷新页面第一次点编辑报错
nextTick
(()
=>
{
formRef
.
value
?.
resetFields
()
})
}
}
</
script
>
</
script
>
src/views/mall/promotion/rewardActivity/components/RewardRule.vue
0 → 100644
View file @
f545dcc6
<
template
>
<!-- 满减送活动规则组件 -->
<el-row>
<template
v-if=
"formData.rules"
>
<el-col
v-for=
"(rule, index) in formData.rules"
:key=
"index"
:span=
"24"
>
<span
class=
"font-bold"
>
活动层级
{{
index
+
1
}}
</span>
<el-button
v-if=
"index !== 0"
link
type=
"danger"
@
click=
"deleteRule(index)"
>
删除
</el-button>
<el-form
ref=
"formRef"
:model=
"rule"
>
<el-form-item
label=
"优惠门槛:"
label-width=
"100px"
prop=
"limit"
>
满
<el-input
v-model=
"rule.limit"
:min=
"0"
class=
"w-150px! p-x-20px!"
placeholder=
""
type=
"number"
/>
<!-- TODO @puhui999:走字典数据? -->
{{
PromotionConditionTypeEnum
.
PRICE
.
type
===
formData
.
conditionType
?
'元'
:
'件'
}}
</el-form-item>
<el-form-item
label=
"优惠内容:"
label-width=
"100px"
>
<el-col
:span=
"24"
>
订单金额优惠
<el-form-item>
减
<el-input-number
v-model=
"rule.discountPrice"
:min=
"0"
:precision=
"2"
:step=
"0.1"
class=
"w-150px! p-x-20px!"
controls-position=
"right"
/>
元
</el-form-item>
</el-col>
<el-col
:span=
"24"
>
<span>
包邮:
</span>
<el-switch
v-model=
"rule.freeDelivery"
active-text=
"是"
inactive-text=
"否"
inline-prompt
/>
</el-col>
<el-col
:span=
"24"
>
<span>
送积分:
</span>
<el-form-item>
送
<el-input
v-model=
"rule.point"
class=
"w-150px! p-x-20px!"
placeholder=
""
type=
"number"
/>
积分
</el-form-item>
</el-col>
<el-col
:span=
"24"
>
<span>
送优惠券:
</span>
<RewardRuleCouponSelect
ref=
"rewardRuleCouponSelectRef"
v-model=
"rule!"
/>
</el-col>
</el-form-item>
</el-form>
</el-col>
</
template
>
<el-col
:span=
"24"
class=
"mt-10px"
>
<el-button
type=
"primary"
@
click=
"addRule"
>
添加优惠规则
</el-button>
</el-col>
<el-col
:span=
"24"
>
<el-tag
type=
"warning"
>
赠送积分为 0 时不赠送。未选优惠券时不赠送。
</el-tag>
</el-col>
</el-row>
</template>
<
script
lang=
"ts"
setup
>
import
RewardRuleCouponSelect
from
'./RewardRuleCouponSelect.vue'
import
{
RewardActivityVO
}
from
'@/api/mall/promotion/reward/rewardActivity'
import
{
PromotionConditionTypeEnum
}
from
'@/utils/constants'
import
{
useVModel
}
from
'@vueuse/core'
import
{
isEmpty
}
from
'@/utils/is'
defineOptions
({
name
:
'RewardRule'
})
const
props
=
defineProps
<
{
modelValue
:
RewardActivityVO
}
>
()
const
emits
=
defineEmits
<
{
(
e
:
'update:modelValue'
,
v
:
any
):
void
(
e
:
'deleteRule'
,
v
:
number
):
void
}
>
()
const
formData
=
useVModel
(
props
,
'modelValue'
,
emits
)
// 活动数据
const
rewardRuleCouponSelectRef
=
ref
<
InstanceType
<
typeof
RewardRuleCouponSelect
>
[]
>
()
// 活动规则优惠券 Ref
/** 删除优惠规则 */
const
deleteRule
=
(
ruleIndex
:
number
)
=>
{
formData
.
value
.
rules
.
splice
(
ruleIndex
,
1
)
}
/** 添加优惠规则 */
const
addRule
=
()
=>
{
if
(
isEmpty
(
formData
.
value
.
rules
))
{
formData
.
value
.
rules
=
[]
}
formData
.
value
.
rules
.
push
({
limit
:
0
,
discountPrice
:
0
,
freeDelivery
:
false
,
point
:
0
})
}
/** 设置规则优惠券-提交时 */
const
setRuleCoupon
=
()
=>
{
if
(
isEmpty
(
rewardRuleCouponSelectRef
.
value
))
{
return
}
rewardRuleCouponSelectRef
.
value
?.
forEach
((
item
)
=>
item
.
setGiveCouponList
())
}
defineExpose
({
setRuleCoupon
})
</
script
>
src/views/mall/promotion/rewardActivity/components/RewardRuleCouponSelect.vue
0 → 100644
View file @
f545dcc6
<
template
>
<el-button
class=
"ml-10px"
type=
"text"
@
click=
"selectCoupon"
>
添加优惠卷
</el-button>
<div
v-for=
"(item, index) in list"
:key=
"item.id"
class=
"coupon-list-item p-x-10px mb-10px flex justify-between"
>
<div
class=
"coupon-list-item-left flex items-center flex-wrap"
>
<div
class=
"mr-10px"
>
优惠券名称:
{{
item
.
name
}}
</div>
<div
class=
"mr-10px"
>
范围:
<dict-tag
:type=
"DICT_TYPE.PROMOTION_PRODUCT_SCOPE"
:value=
"item.productScope"
/>
</div>
<div
class=
"flex items-center"
>
优惠:
<dict-tag
:type=
"DICT_TYPE.PROMOTION_DISCOUNT_TYPE"
:value=
"item.discountType"
/>
{{
discountFormat
(
item
)
}}
</div>
</div>
<div
class=
"coupon-list-item-right"
>
送
<el-input
v-model=
"item.giveCount"
class=
"w-150px! p-x-20px!"
placeholder=
""
type=
"number"
/>
张
<el-button
class=
"ml-20px"
link
type=
"danger"
@
click=
"deleteCoupon(index)"
>
删除
</el-button>
</div>
</div>
<!-- 优惠券选择 -->
<CouponSelect
ref=
"couponSelectRef"
:take-type=
"CouponTemplateTakeTypeEnum.ADMIN.type"
@
change=
"handleCouponChange"
/>
</
template
>
<
script
lang=
"ts"
setup
>
import
{
CouponSelect
}
from
'@/views/mall/promotion/coupon/components'
import
*
as
CouponTemplateApi
from
'@/api/mall/promotion/coupon/couponTemplate'
import
{
RewardRule
}
from
'@/api/mall/promotion/reward/rewardActivity'
import
{
DICT_TYPE
}
from
'@/utils/dict'
import
{
CouponTemplateTakeTypeEnum
}
from
'@/utils/constants'
import
{
discountFormat
}
from
'@/views/mall/promotion/coupon/formatter'
import
{
isEmpty
}
from
'@/utils/is'
import
{
useVModel
}
from
'@vueuse/core'
defineOptions
({
name
:
'RewardRuleCouponSelect'
})
const
props
=
defineProps
<
{
modelValue
:
RewardRule
}
>
()
const
emits
=
defineEmits
<
{
(
e
:
'update:modelValue'
,
v
:
any
):
void
}
>
()
const
rewardRule
=
useVModel
(
props
,
'modelValue'
,
emits
)
// 赠送规则
const
list
=
ref
<
GiveCouponVO
[]
>
([])
// 选择的优惠券列表
/** 选择赠送的优惠卷类型拓展 */
interface
GiveCouponVO
extends
CouponTemplateApi
.
CouponTemplateVO
{
giveCount
?:
number
}
/** 选择优惠券 */
const
couponSelectRef
=
ref
<
InstanceType
<
typeof
CouponSelect
>>
()
// 优惠券选择
const
selectCoupon
=
()
=>
{
couponSelectRef
.
value
?.
open
()
}
/** 选择优惠券后的回调 */
const
handleCouponChange
=
(
val
:
CouponTemplateApi
.
CouponTemplateVO
[])
=>
{
for
(
const
item
of
val
)
{
if
(
list
.
value
.
some
((
v
)
=>
v
.
id
===
item
.
id
))
{
continue
}
list
.
value
.
push
(
item
)
}
}
/** 删除优惠券 */
const
deleteCoupon
=
(
index
:
number
)
=>
{
list
.
value
.
splice
(
index
,
1
)
}
/** 初始化赠送的优惠券列表 */
const
initGiveCouponList
=
async
()
=>
{
// 校验优惠券存在
if
(
isEmpty
(
rewardRule
.
value
)
||
isEmpty
(
rewardRule
.
value
.
giveCouponTemplateCounts
))
{
return
}
const
tempLateIds
=
Object
.
keys
(
rewardRule
.
value
.
giveCouponTemplateCounts
!
).
map
((
item
)
=>
parseInt
(
item
)
)
const
data
=
await
CouponTemplateApi
.
getCouponTemplateList
(
tempLateIds
)
if
(
!
data
)
{
return
}
// 回显
data
.
forEach
((
coupon
)
=>
{
list
.
value
.
push
({
...
coupon
,
giveCount
:
rewardRule
.
value
.
giveCouponTemplateCounts
!
[
coupon
.
id
]
})
})
}
/** 设置赠送的优惠券 */
const
setGiveCouponList
=
()
=>
{
if
(
isEmpty
(
rewardRule
.
value
)
||
isEmpty
(
list
.
value
))
{
return
}
// 设置优惠券和其数量的对应
list
.
value
.
forEach
((
rule
)
=>
{
if
(
!
rewardRule
.
value
.
giveCouponTemplateCounts
)
{
rewardRule
.
value
.
giveCouponTemplateCounts
=
{}
}
rewardRule
.
value
.
giveCouponTemplateCounts
[
rule
.
id
]
=
rule
.
giveCount
!
})
}
defineExpose
({
setGiveCouponList
})
/** 组件初始化 */
onMounted
(
async
()
=>
{
await
nextTick
()
await
initGiveCouponList
()
})
</
script
>
<
style
lang=
"scss"
scoped
>
.coupon-list-item
{
border
:
1px
dashed
var
(
--el-border-color-darker
);
border-radius
:
8px
;
}
</
style
>
src/views/mall/promotion/rewardActivity/index.vue
View file @
f545dcc6
...
@@ -4,27 +4,27 @@
...
@@ -4,27 +4,27 @@
<!-- 搜索工作栏 -->
<!-- 搜索工作栏 -->
<ContentWrap>
<ContentWrap>
<el-form
<el-form
class=
"-mb-15px"
:model=
"queryParams"
ref=
"queryFormRef"
ref=
"queryFormRef"
:inline=
"true"
:inline=
"true"
:model=
"queryParams"
class=
"-mb-15px"
label-width=
"68px"
label-width=
"68px"
>
>
<el-form-item
label=
"活动名称"
prop=
"name"
>
<el-form-item
label=
"活动名称"
prop=
"name"
>
<el-input
<el-input
v-model=
"queryParams.name"
v-model=
"queryParams.name"
placeholder=
"请输入活动名称
"
class=
"!w-240px
"
clearable
clearable
placeholder=
"请输入活动名称"
@
keyup
.
enter=
"handleQuery"
@
keyup
.
enter=
"handleQuery"
class=
"!w-240px"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
label=
"活动状态"
prop=
"status"
>
<el-form-item
label=
"活动状态"
prop=
"status"
>
<el-select
<el-select
v-model=
"queryParams.status"
v-model=
"queryParams.status"
placeholder=
"请选择活动状态"
clearable
class=
"!w-240px"
class=
"!w-240px"
clearable
placeholder=
"请选择活动状态"
>
>
<el-option
<el-option
v-for=
"dict in getIntDictOptions(DICT_TYPE.PROMOTION_ACTIVITY_STATUS)"
v-for=
"dict in getIntDictOptions(DICT_TYPE.PROMOTION_ACTIVITY_STATUS)"
...
@@ -37,24 +37,31 @@
...
@@ -37,24 +37,31 @@
<el-form-item
label=
"活动时间"
prop=
"createTime"
>
<el-form-item
label=
"活动时间"
prop=
"createTime"
>
<el-date-picker
<el-date-picker
v-model=
"queryParams.createTime"
v-model=
"queryParams.createTime"
value-format=
"YYYY-MM-DD HH:mm:ss"
type=
"daterange"
start-placeholder=
"活动开始日期"
end-placeholder=
"活动结束日期"
:default-time=
"[new Date('1 00:00:00'), new Date('1 23:59:59')]"
:default-time=
"[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class=
"!w-240px"
class=
"!w-240px"
end-placeholder=
"活动结束日期"
start-placeholder=
"活动开始日期"
type=
"daterange"
value-format=
"YYYY-MM-DD HH:mm:ss"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item>
<el-form-item>
<el-button
@
click=
"handleQuery"
><Icon
icon=
"ep:search"
class=
"mr-5px"
/>
搜索
</el-button>
<el-button
@
click=
"handleQuery"
>
<el-button
@
click=
"resetQuery"
><Icon
icon=
"ep:refresh"
class=
"mr-5px"
/>
重置
</el-button>
<Icon
class=
"mr-5px"
icon=
"ep:search"
/>
搜索
</el-button>
<el-button
@
click=
"resetQuery"
>
<Icon
class=
"mr-5px"
icon=
"ep:refresh"
/>
重置
</el-button>
<el-button
<el-button
type=
"primary
"
v-hasPermi=
"['product:brand:create']
"
plain
plain
type=
"primary"
@
click=
"openForm('create')"
@
click=
"openForm('create')"
v-hasPermi=
"['product:brand:create']"
>
>
<Icon
icon=
"ep:plus"
class=
"mr-5px"
/>
新增
<Icon
class=
"mr-5px"
icon=
"ep:plus"
/>
新增
</el-button>
</el-button>
</el-form-item>
</el-form-item>
</el-form>
</el-form>
...
@@ -62,47 +69,47 @@
...
@@ -62,47 +69,47 @@
<!-- 列表 -->
<!-- 列表 -->
<ContentWrap>
<ContentWrap>
<el-table
v-loading=
"loading"
:data=
"list"
row-key=
"id"
default-expand-all
>
<el-table
v-loading=
"loading"
:data=
"list"
default-expand-all
row-key=
"id"
>
<el-table-column
label=
"活动名称"
prop=
"name"
/>
<el-table-column
label=
"活动名称"
prop=
"name"
/>
<el-table-column
<el-table-column
label=
"活动开始时间
"
:formatter=
"dateFormatter
"
align=
"center"
align=
"center"
label=
"活动开始时间"
prop=
"startTime"
prop=
"startTime"
:formatter=
"dateFormatter"
/>
/>
<el-table-column
<el-table-column
label=
"活动结束时间
"
:formatter=
"dateFormatter
"
align=
"center"
align=
"center"
label=
"活动结束时间"
prop=
"endTime"
prop=
"endTime"
:formatter=
"dateFormatter"
/>
/>
<el-table-column
label=
"状态"
align=
"center
"
prop=
"status"
>
<el-table-column
align=
"center"
label=
"状态
"
prop=
"status"
>
<template
#
default=
"scope"
>
<template
#
default=
"scope"
>
<dict-tag
:type=
"DICT_TYPE.PROMOTION_ACTIVITY_STATUS"
:value=
"scope.row.status"
/>
<dict-tag
:type=
"DICT_TYPE.PROMOTION_ACTIVITY_STATUS"
:value=
"scope.row.status"
/>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
<el-table-column
label=
"创建时间
"
:formatter=
"dateFormatter
"
align=
"center"
align=
"center"
label=
"创建时间"
prop=
"createTime"
prop=
"createTime"
width=
"180"
width=
"180"
:formatter=
"dateFormatter"
/>
/>
<el-table-column
label=
"操作"
align=
"center
"
>
<el-table-column
align=
"center"
label=
"操作
"
>
<
template
#
default=
"scope"
>
<
template
#
default=
"scope"
>
<el-button
<el-button
v-hasPermi=
"['product:brand:update']"
link
link
type=
"primary"
type=
"primary"
@
click=
"openForm('update', scope.row.id)"
@
click=
"openForm('update', scope.row.id)"
v-hasPermi=
"['product:brand:update']"
>
>
编辑
编辑
</el-button>
</el-button>
<el-button
<el-button
v-hasPermi=
"['product:brand:delete']"
link
link
type=
"danger"
type=
"danger"
@
click=
"handleDelete(scope.row.id)"
@
click=
"handleDelete(scope.row.id)"
v-hasPermi=
"['product:brand:delete']"
>
>
删除
删除
</el-button>
</el-button>
...
@@ -111,9 +118,9 @@
...
@@ -111,9 +118,9 @@
</el-table>
</el-table>
<!-- 分页 -->
<!-- 分页 -->
<Pagination
<Pagination
:total=
"total"
v-model:page=
"queryParams.pageNo"
v-model:limit=
"queryParams.pageSize"
v-model:limit=
"queryParams.pageSize"
v-model:page=
"queryParams.pageNo"
:total=
"total"
@
pagination=
"getList"
@
pagination=
"getList"
/>
/>
</ContentWrap>
</ContentWrap>
...
@@ -168,9 +175,9 @@ const resetQuery = () => {
...
@@ -168,9 +175,9 @@ const resetQuery = () => {
}
}
/** 添加/修改操作 */
/** 添加/修改操作 */
const
formRef
=
ref
()
const
formRef
=
ref
<
InstanceType
<
typeof
RewardForm
>>
()
const
openForm
=
(
type
:
string
,
id
?:
number
)
=>
{
const
openForm
=
(
type
:
string
,
id
?:
number
)
=>
{
formRef
.
value
.
open
(
type
,
id
)
formRef
.
value
?
.
open
(
type
,
id
)
}
}
/** 删除按钮操作 */
/** 删除按钮操作 */
...
...
src/views/mall/promotion/seckill/components/SeckillShowcase.vue
0 → 100644
View file @
f545dcc6
<
template
>
<div
class=
"flex flex-wrap items-center gap-8px"
>
<div
v-for=
"(seckillActivity, index) in Activitys"
:key=
"seckillActivity.id"
class=
"select-box spu-pic"
>
<el-tooltip
:content=
"seckillActivity.name"
>
<div
class=
"relative h-full w-full"
>
<el-image
:src=
"seckillActivity.picUrl"
class=
"h-full w-full"
/>
<Icon
v-show=
"!disabled"
class=
"del-icon"
icon=
"ep:circle-close-filled"
@
click=
"handleRemoveActivity(index)"
/>
</div>
</el-tooltip>
</div>
<el-tooltip
content=
"选择活动"
v-if=
"canAdd"
>
<div
class=
"select-box"
@
click=
"openSeckillActivityTableSelect"
>
<Icon
icon=
"ep:plus"
/>
</div>
</el-tooltip>
</div>
<!-- 拼团活动选择对话框(表格形式) -->
<SeckillTableSelect
ref=
"seckillActivityTableSelectRef"
:multiple=
"limit != 1"
@
change=
"handleActivitySelected"
/>
</
template
>
<
script
lang=
"ts"
setup
>
import
*
as
SeckillActivityApi
from
'@/api/mall/promotion/seckill/seckillActivity'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
oneOfType
}
from
'vue-types'
import
{
isArray
}
from
'@/utils/is'
import
SeckillTableSelect
from
'@/views/mall/promotion/seckill/components/SeckillTableSelect.vue'
// 活动橱窗,一般用于装修时使用
// 提供功能:展示活动列表、添加活动、删除活动
defineOptions
({
name
:
'SeckillShowcase'
})
const
props
=
defineProps
({
modelValue
:
oneOfType
<
number
|
Array
<
number
>>
([
Number
,
Array
]).
isRequired
,
// 限制数量:默认不限制
limit
:
propTypes
.
number
.
def
(
Number
.
MAX_VALUE
),
disabled
:
propTypes
.
bool
.
def
(
false
)
})
// 计算是否可以添加
const
canAdd
=
computed
(()
=>
{
// 情况一:禁用时不可以添加
if
(
props
.
disabled
)
return
false
// 情况二:未指定限制数量时,可以添加
if
(
!
props
.
limit
)
return
true
// 情况三:检查已添加数量是否小于限制数量
return
Activitys
.
value
.
length
<
props
.
limit
})
// 拼团活动列表
const
Activitys
=
ref
<
SeckillActivityApi
.
SeckillActivityVO
[]
>
([])
watch
(
()
=>
props
.
modelValue
,
async
()
=>
{
const
ids
=
isArray
(
props
.
modelValue
)
?
// 情况一:多选
props
.
modelValue
:
// 情况二:单选
props
.
modelValue
?
[
props
.
modelValue
]
:
[]
// 不需要返显
if
(
ids
.
length
===
0
)
{
Activitys
.
value
=
[]
return
}
// 只有活动发生变化之后,才会查询活动
if
(
Activitys
.
value
.
length
===
0
||
Activitys
.
value
.
some
((
seckillActivity
)
=>
!
ids
.
includes
(
seckillActivity
.
id
!
))
)
{
Activitys
.
value
=
await
SeckillActivityApi
.
getSeckillActivityListByIds
(
ids
)
}
},
{
immediate
:
true
}
)
/** 活动表格选择对话框 */
const
seckillActivityTableSelectRef
=
ref
()
// 打开对话框
const
openSeckillActivityTableSelect
=
()
=>
{
seckillActivityTableSelectRef
.
value
.
open
(
Activitys
.
value
)
}
/**
* 选择活动后触发
* @param activityVOs 选中的活动列表
*/
const
handleActivitySelected
=
(
activityVOs
:
SeckillActivityApi
.
SeckillActivityVO
|
SeckillActivityApi
.
SeckillActivityVO
[]
)
=>
{
Activitys
.
value
=
isArray
(
activityVOs
)
?
activityVOs
:
[
activityVOs
]
emitActivityChange
()
}
/**
* 删除活动
* @param index 活动索引
*/
const
handleRemoveActivity
=
(
index
:
number
)
=>
{
Activitys
.
value
.
splice
(
index
,
1
)
emitActivityChange
()
}
const
emit
=
defineEmits
([
'update:modelValue'
,
'change'
])
const
emitActivityChange
=
()
=>
{
if
(
props
.
limit
===
1
)
{
const
seckillActivity
=
Activitys
.
value
.
length
>
0
?
Activitys
.
value
[
0
]
:
null
emit
(
'update:modelValue'
,
seckillActivity
?.
id
||
0
)
emit
(
'change'
,
seckillActivity
)
}
else
{
emit
(
'update:modelValue'
,
Activitys
.
value
.
map
((
seckillActivity
)
=>
seckillActivity
.
id
)
)
emit
(
'change'
,
Activitys
.
value
)
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.select-box
{
display
:
flex
;
width
:
60px
;
height
:
60px
;
border
:
1px
dashed
var
(
--el-border-color-darker
);
border-radius
:
8px
;
align-items
:
center
;
justify-content
:
center
;
cursor
:
pointer
;
}
.spu-pic
{
position
:
relative
;
}
.del-icon
{
position
:
absolute
;
top
:
-10px
;
right
:
-10px
;
z-index
:
1
;
width
:
20px
!important
;
height
:
20px
!important
;
}
</
style
>
src/views/mall/promotion/seckill/components/SeckillTableSelect.vue
0 → 100644
View file @
f545dcc6
<
template
>
<Dialog
v-model=
"dialogVisible"
:appendToBody=
"true"
title=
"选择活动"
width=
"70%"
>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
ref=
"queryFormRef"
:inline=
"true"
:model=
"queryParams"
class=
"-mb-15px"
label-width=
"68px"
>
<el-form-item
label=
"活动名称"
prop=
"name"
>
<el-input
v-model=
"queryParams.name"
placeholder=
"请输入活动名称"
clearable
@
keyup
.
enter=
"handleQuery"
class=
"!w-240px"
/>
</el-form-item>
<el-form-item
label=
"活动状态"
prop=
"status"
>
<el-select
v-model=
"queryParams.status"
placeholder=
"请选择活动状态"
clearable
class=
"!w-240px"
>
<el-option
v-for=
"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key=
"dict.value"
:label=
"dict.label"
:value=
"dict.value"
/>
</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-form-item>
</el-form>
<el-table
v-loading=
"loading"
:data=
"list"
show-overflow-tooltip
>
<!-- 1. 多选模式(不能使用type="selection",Element会忽略Header插槽) -->
<el-table-column
width=
"55"
v-if=
"multiple"
>
<template
#
header
>
<el-checkbox
v-model=
"isCheckAll"
:indeterminate=
"isIndeterminate"
@
change=
"handleCheckAll"
/>
</
template
>
<
template
#
default=
"{ row }"
>
<el-checkbox
v-model=
"checkedStatus[row.id]"
@
change=
"(checked: boolean) => handleCheckOne(checked, row, true)"
/>
</
template
>
</el-table-column>
<!-- 2. 单选模式 -->
<el-table-column
label=
"#"
width=
"55"
v-else
>
<
template
#
default=
"{ row }"
>
<el-radio
:value=
"row.id"
v-model=
"selectedActivityId"
@
change=
"handleSingleSelected(row)"
>
<!-- 空格不能省略,是为了让单选框不显示label,如果不指定label不会有选中的效果 -->
</el-radio>
</
template
>
</el-table-column>
<el-table-column
label=
"活动编号"
prop=
"id"
min-width=
"80"
/>
<el-table-column
label=
"活动名称"
prop=
"name"
min-width=
"140"
/>
<el-table-column
label=
"活动时间"
min-width=
"210"
>
<
template
#
default=
"scope"
>
{{
formatDate
(
scope
.
row
.
startTime
,
'YYYY-MM-DD'
)
}}
~
{{
formatDate
(
scope
.
row
.
endTime
,
'YYYY-MM-DD'
)
}}
</
template
>
</el-table-column>
<el-table-column
label=
"商品图片"
prop=
"spuName"
min-width=
"80"
>
<
template
#
default=
"scope"
>
<el-image
:src=
"scope.row.picUrl"
class=
"h-40px w-40px"
:preview-src-list=
"[scope.row.picUrl]"
preview-teleported
/>
</
template
>
</el-table-column>
<el-table-column
label=
"商品标题"
prop=
"spuName"
min-width=
"300"
/>
<el-table-column
label=
"原价"
prop=
"marketPrice"
min-width=
"100"
:formatter=
"fenToYuanFormat"
/>
<el-table-column
label=
"拼团价"
prop=
"seckillPrice"
min-width=
"100"
>
<
template
#
default=
"scope"
>
{{
formatSeckillPrice
(
scope
.
row
.
products
)
}}
</
template
>
</el-table-column>
<el-table-column
label=
"开团组数"
prop=
"groupCount"
min-width=
"100"
/>
<el-table-column
label=
"成团组数"
prop=
"groupSuccessCount"
min-width=
"100"
/>
<el-table-column
label=
"购买次数"
prop=
"recordCount"
min-width=
"100"
/>
<el-table-column
label=
"活动状态"
align=
"center"
prop=
"status"
min-width=
"100"
>
<
template
#
default=
"scope"
>
<dict-tag
:type=
"DICT_TYPE.COMMON_STATUS"
:value=
"scope.row.status"
/>
</
template
>
</el-table-column>
<el-table-column
label=
"创建时间"
align=
"center"
prop=
"createTime"
:formatter=
"dateFormatter"
width=
"180px"
/>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit=
"queryParams.pageSize"
v-model:page=
"queryParams.pageNo"
:total=
"total"
@
pagination=
"getList"
/>
</ContentWrap>
<
template
#
footer
v-if=
"multiple"
>
<el-button
type=
"primary"
@
click=
"handleEmitChange"
>
确 定
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</Dialog>
</template>
<
script
lang=
"ts"
setup
>
import
{
handleTree
}
from
'@/utils/tree'
import
*
as
ProductCategoryApi
from
'@/api/mall/product/category'
import
{
propTypes
}
from
'@/utils/propTypes'
import
{
CHANGE_EVENT
}
from
'element-plus'
import
*
as
SeckillActivityApi
from
'@/api/mall/promotion/seckill/seckillActivity'
import
{
fenToYuanFormat
}
from
'@/utils/formatter'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
dateFormatter
,
formatDate
}
from
'@/utils/formatTime'
import
{
fenToYuan
}
from
'@/utils'
type
SeckillActivityVO
=
Required
<
SeckillActivityApi
.
SeckillActivityVO
>
/**
* 活动表格选择对话框
* 1. 单选模式:
* 1.1 点击表格左侧的单选框时,结束选择,并关闭对话框
* 1.2 再次打开时,保持选中状态
* 2. 多选模式:
* 2.1 点击表格左侧的多选框时,记录选中的活动
* 2.2 切换分页时,保持活动的选中状态
* 2.3 点击右下角的确定按钮时,结束选择,关闭对话框
* 2.4 再次打开时,保持选中状态
*/
defineOptions
({
name
:
'SeckillTableSelect'
})
defineProps
({
// 多选模式
multiple
:
propTypes
.
bool
.
def
(
false
)
})
// 列表的总页数
const
total
=
ref
(
0
)
// 列表的数据
const
list
=
ref
<
SeckillActivityVO
[]
>
([])
// 列表的加载中
const
loading
=
ref
(
false
)
// 弹窗的是否展示
const
dialogVisible
=
ref
(
false
)
// 查询参数
const
queryParams
=
ref
({
pageNo
:
1
,
pageSize
:
10
,
name
:
null
,
status
:
undefined
})
/** 打开弹窗 */
const
open
=
(
SeckillList
?:
SeckillActivityVO
[])
=>
{
// 重置
checkedActivitys
.
value
=
[]
checkedStatus
.
value
=
{}
isCheckAll
.
value
=
false
isIndeterminate
.
value
=
false
// 处理已选中
if
(
SeckillList
&&
SeckillList
.
length
>
0
)
{
checkedActivitys
.
value
=
[...
SeckillList
]
checkedStatus
.
value
=
Object
.
fromEntries
(
SeckillList
.
map
((
activityVO
)
=>
[
activityVO
.
id
,
true
]))
}
dialogVisible
.
value
=
true
resetQuery
()
}
// 提供 open 方法,用于打开弹窗
defineExpose
({
open
})
/** 查询列表 */
const
getList
=
async
()
=>
{
loading
.
value
=
true
try
{
const
data
=
await
SeckillActivityApi
.
getSeckillActivityPage
(
queryParams
.
value
)
list
.
value
=
data
.
list
total
.
value
=
data
.
total
// checkbox绑定undefined会有问题,需要给一个bool值
list
.
value
.
forEach
(
(
activityVO
)
=>
(
checkedStatus
.
value
[
activityVO
.
id
]
=
checkedStatus
.
value
[
activityVO
.
id
]
||
false
)
)
// 计算全选框状态
calculateIsCheckAll
()
}
finally
{
loading
.
value
=
false
}
}
/** 搜索按钮操作 */
const
handleQuery
=
()
=>
{
queryParams
.
value
.
pageNo
=
1
getList
()
}
/** 重置按钮操作 */
const
resetQuery
=
()
=>
{
queryParams
.
value
=
{
pageNo
:
1
,
pageSize
:
10
,
name
:
undefined
,
createTime
:
[]
}
getList
()
}
/**
* 格式化拼团价格
* @param products
*/
const
formatSeckillPrice
=
(
products
)
=>
{
const
seckillPrice
=
Math
.
min
(...
products
.
map
((
item
)
=>
item
.
seckillPrice
))
return
`¥
${
fenToYuan
(
seckillPrice
)}
`
}
// 是否全选
const
isCheckAll
=
ref
(
false
)
// 全选框是否处于中间状态:不是全部选中 && 任意一个选中
const
isIndeterminate
=
ref
(
false
)
// 选中的活动
const
checkedActivitys
=
ref
<
SeckillActivityVO
[]
>
([])
// 选中状态:key为活动ID,value为是否选中
const
checkedStatus
=
ref
<
Record
<
string
,
boolean
>>
({})
// 选中的活动 activityId
const
selectedActivityId
=
ref
()
/** 单选中时触发 */
const
handleSingleSelected
=
(
seckillActivityVO
:
SeckillActivityVO
)
=>
{
emits
(
CHANGE_EVENT
,
seckillActivityVO
)
// 关闭弹窗
dialogVisible
.
value
=
false
// 记住上次选择的ID
selectedActivityId
.
value
=
seckillActivityVO
.
id
}
/** 多选完成 */
const
handleEmitChange
=
()
=>
{
// 关闭弹窗
dialogVisible
.
value
=
false
emits
(
CHANGE_EVENT
,
[...
checkedActivitys
.
value
])
}
/** 确认选择时的触发事件 */
const
emits
=
defineEmits
<
{
change
:
[
SeckillActivityApi
:
SeckillActivityVO
|
SeckillActivityVO
[]
|
any
]
}
>
()
/** 全选/全不选 */
const
handleCheckAll
=
(
checked
:
boolean
)
=>
{
isCheckAll
.
value
=
checked
isIndeterminate
.
value
=
false
list
.
value
.
forEach
((
seckillActivity
)
=>
handleCheckOne
(
checked
,
seckillActivity
,
false
))
}
/**
* 选中一行
* @param checked 是否选中
* @param seckillActivity 活动
* @param isCalcCheckAll 是否计算全选
*/
const
handleCheckOne
=
(
checked
:
boolean
,
seckillActivity
:
SeckillActivityVO
,
isCalcCheckAll
:
boolean
)
=>
{
if
(
checked
)
{
checkedActivitys
.
value
.
push
(
seckillActivity
)
checkedStatus
.
value
[
seckillActivity
.
id
]
=
true
}
else
{
const
index
=
findCheckedIndex
(
seckillActivity
)
if
(
index
>
-
1
)
{
checkedActivitys
.
value
.
splice
(
index
,
1
)
checkedStatus
.
value
[
seckillActivity
.
id
]
=
false
isCheckAll
.
value
=
false
}
}
// 计算全选框状态
if
(
isCalcCheckAll
)
{
calculateIsCheckAll
()
}
}
// 查找活动在已选中活动列表中的索引
const
findCheckedIndex
=
(
activityVO
:
SeckillActivityVO
)
=>
checkedActivitys
.
value
.
findIndex
((
item
)
=>
item
.
id
===
activityVO
.
id
)
// 计算全选框状态
const
calculateIsCheckAll
=
()
=>
{
isCheckAll
.
value
=
list
.
value
.
every
((
activityVO
)
=>
checkedStatus
.
value
[
activityVO
.
id
])
// 计算中间状态:不是全部选中 && 任意一个选中
isIndeterminate
.
value
=
!
isCheckAll
.
value
&&
list
.
value
.
some
((
activityVO
)
=>
checkedStatus
.
value
[
activityVO
.
id
])
}
// 分类列表
const
categoryList
=
ref
()
// 分类树
const
categoryTreeList
=
ref
()
/** 初始化 **/
onMounted
(
async
()
=>
{
await
getList
()
// 获得分类树
categoryList
.
value
=
await
ProductCategoryApi
.
getCategoryList
({})
categoryTreeList
.
value
=
handleTree
(
categoryList
.
value
,
'id'
,
'parentId'
)
})
</
script
>
src/views/mall/trade/afterSale/index.vue
View file @
f545dcc6
...
@@ -135,7 +135,7 @@
...
@@ -135,7 +135,7 @@
</div>
</div>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
align=
"center"
label=
"订单金额"
prop=
"refundPrice"
min-width=
"120
"
>
<el-table-column
align=
"center"
label=
"订单金额"
min-width=
"120"
prop=
"refundPrice
"
>
<
template
#
default=
"scope"
>
<
template
#
default=
"scope"
>
<span>
{{
fenToYuan
(
scope
.
row
.
refundPrice
)
}}
元
</span>
<span>
{{
fenToYuan
(
scope
.
row
.
refundPrice
)
}}
元
</span>
</
template
>
</
template
>
...
...
src/views/member/user/components/UserBalanceUpdateForm.vue
0 → 100644
View file @
f545dcc6
<
template
>
<Dialog
v-model=
"dialogVisible"
title=
"修改用户余额"
width=
"600"
>
<el-form
ref=
"formRef"
v-loading=
"formLoading"
:model=
"formData"
:rules=
"formRules"
label-width=
"130px"
>
<el-form-item
label=
"用户编号"
prop=
"id"
>
<el-input
v-model=
"formData.id"
class=
"!w-240px"
disabled
/>
</el-form-item>
<el-form-item
label=
"用户昵称"
prop=
"nickname"
>
<el-input
v-model=
"formData.nickname"
class=
"!w-240px"
disabled
/>
</el-form-item>
<el-form-item
label=
"变动前余额(元)"
prop=
"balance"
>
<el-input
:model-value=
"formData.balance"
class=
"!w-240px"
disabled
/>
</el-form-item>
<el-form-item
label=
"变动类型"
prop=
"changeType"
>
<el-radio-group
v-model=
"formData.changeType"
>
<el-radio
:label=
"1"
>
增加
</el-radio>
<el-radio
:label=
"-1"
>
减少
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
label=
"变动余额(元)"
prop=
"changeBalance"
>
<el-input-number
v-model=
"formData.changeBalance"
:min=
"0"
:precision=
"2"
:step=
"0.1"
class=
"!w-240px"
/>
</el-form-item>
<el-form-item
label=
"变动后余额(元)"
>
<el-input
:model-value=
"balanceResult"
class=
"!w-240px"
disabled
/>
</el-form-item>
</el-form>
<template
#
footer
>
<el-button
:disabled=
"formLoading"
type=
"primary"
@
click=
"submitForm"
>
确 定
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</Dialog>
</template>
<
script
lang=
"ts"
setup
>
import
*
as
UserApi
from
'@/api/member/user'
import
*
as
WalletApi
from
'@/api/pay/wallet/balance'
import
{
convertToInteger
,
formatToFraction
}
from
'@/utils'
/** 修改用户余额表单 */
defineOptions
({
name
:
'UpdateBalanceForm'
})
const
{
t
}
=
useI18n
()
// 国际化
const
message
=
useMessage
()
// 消息弹窗
const
dialogVisible
=
ref
(
false
)
// 弹窗的是否展示
const
formLoading
=
ref
(
false
)
// 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const
formData
=
ref
({
id
:
undefined
,
nickname
:
undefined
,
balance
:
'0'
,
changeBalance
:
0
,
changeType
:
1
})
const
formRules
=
reactive
({
changeBalance
:
[{
required
:
true
,
message
:
'变动余额不能为空'
,
trigger
:
'blur'
}]
})
const
formRef
=
ref
()
// 表单 Ref
/** 打开弹窗 */
const
open
=
async
(
id
?:
number
)
=>
{
dialogVisible
.
value
=
true
resetForm
()
// 修改时,设置数据
if
(
id
)
{
formLoading
.
value
=
true
try
{
const
user
=
await
UserApi
.
getUser
(
id
)
const
wallet
=
await
WalletApi
.
getWallet
({
userId
:
user
.
id
||
0
})
formData
.
value
.
id
=
user
.
id
formData
.
value
.
nickname
=
user
.
nickname
formData
.
value
.
balance
=
formatToFraction
(
wallet
.
balance
)
formData
.
value
.
changeType
=
1
// 默认增加余额
formData
.
value
.
changeBalance
=
0
// 变动余额默认0
}
finally
{
formLoading
.
value
=
false
}
}
}
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
/** 提交表单 */
const
emit
=
defineEmits
([
'success'
])
// 定义 success 事件,用于操作成功后的回调
const
submitForm
=
async
()
=>
{
// 校验表单
if
(
!
formRef
)
return
const
valid
=
await
formRef
.
value
.
validate
()
if
(
!
valid
)
return
if
(
formData
.
value
.
changeBalance
<=
0
)
{
message
.
error
(
'变动余额不能为零'
)
return
}
if
(
convertToInteger
(
balanceResult
.
value
)
<
0
)
{
message
.
error
(
'变动后的余额不能小于 0'
)
return
}
// 提交请求
formLoading
.
value
=
true
try
{
await
WalletApi
.
updateWalletBalance
({
userId
:
formData
.
value
.
id
,
balance
:
convertToInteger
(
formData
.
value
.
changeBalance
)
*
formData
.
value
.
changeType
})
message
.
success
(
t
(
'common.updateSuccess'
))
dialogVisible
.
value
=
false
// 发送操作成功的事件
emit
(
'success'
)
}
finally
{
formLoading
.
value
=
false
}
}
/** 重置表单 */
const
resetForm
=
()
=>
{
formData
.
value
=
{
id
:
undefined
,
nickname
:
undefined
,
balance
:
'0'
,
changeBalance
:
0
,
changeType
:
1
}
formRef
.
value
?.
resetFields
()
}
/** 变动后的余额 */
const
balanceResult
=
computed
(()
=>
formatToFraction
(
convertToInteger
(
formData
.
value
.
balance
)
+
convertToInteger
(
formData
.
value
.
changeBalance
)
*
formData
.
value
.
changeType
)
)
</
script
>
src/views/member/user/UserLevelUpdateForm.vue
→
src/views/member/user/
components/
UserLevelUpdateForm.vue
View file @
f545dcc6
File moved
src/views/member/user/UserPointUpdateForm.vue
→
src/views/member/user/
components/
UserPointUpdateForm.vue
View file @
f545dcc6
<
template
>
<
template
>
<Dialog
title=
"修改用户积分"
v-model=
"dialogVisible
"
width=
"600"
>
<Dialog
v-model=
"dialogVisible"
title=
"修改用户积分
"
width=
"600"
>
<el-form
<el-form
ref=
"formRef"
ref=
"formRef"
v-loading=
"formLoading"
:model=
"formData"
:model=
"formData"
:rules=
"formRules"
:rules=
"formRules"
label-width=
"100px"
label-width=
"100px"
v-loading=
"formLoading"
>
>
<el-form-item
label=
"用户编号"
prop=
"id"
>
<el-form-item
label=
"用户编号"
prop=
"id"
>
<el-input
v-model=
"formData.id"
class=
"!w-240px"
disabled
/>
<el-input
v-model=
"formData.id"
class=
"!w-240px"
disabled
/>
...
@@ -23,19 +23,19 @@
...
@@ -23,19 +23,19 @@
</el-radio-group>
</el-radio-group>
</el-form-item>
</el-form-item>
<el-form-item
label=
"变动积分"
prop=
"changePoint"
>
<el-form-item
label=
"变动积分"
prop=
"changePoint"
>
<el-input-number
v-model=
"formData.changePoint"
class=
"!w-240px"
:min=
"0"
:precision=
"0
"
/>
<el-input-number
v-model=
"formData.changePoint"
:min=
"0"
:precision=
"0"
class=
"!w-240px
"
/>
</el-form-item>
</el-form-item>
<el-form-item
label=
"变动后积分"
>
<el-form-item
label=
"变动后积分"
>
<el-input-number
v-model=
"pointResult"
class=
"!w-240px"
disabled
/>
<el-input-number
v-model=
"pointResult"
class=
"!w-240px"
disabled
/>
</el-form-item>
</el-form-item>
</el-form>
</el-form>
<template
#
footer
>
<template
#
footer
>
<el-button
@
click=
"submitForm"
type=
"primary"
:disabled=
"formLoading
"
>
确 定
</el-button>
<el-button
:disabled=
"formLoading"
type=
"primary"
@
click=
"submitForm
"
>
确 定
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</
template
>
</Dialog>
</Dialog>
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
lang=
"ts"
setup
>
import
*
as
UserApi
from
'@/api/member/user'
import
*
as
UserApi
from
'@/api/member/user'
/** 修改用户积分表单 */
/** 修改用户积分表单 */
...
@@ -115,8 +115,9 @@ const resetForm = () => {
...
@@ -115,8 +115,9 @@ const resetForm = () => {
formData
.
value
=
{
formData
.
value
=
{
id
:
undefined
,
id
:
undefined
,
nickname
:
undefined
,
nickname
:
undefined
,
levelId
:
undefined
,
point
:
0
,
reason
:
undefined
changePoint
:
0
,
changeType
:
1
}
}
formRef
.
value
?.
resetFields
()
formRef
.
value
?.
resetFields
()
}
}
...
...
src/views/member/user/components/balance-list.vue
deleted
100644 → 0
View file @
09d1dbed
<
script
lang=
"ts"
>
import
{
defineComponent
}
from
'vue'
export
default
defineComponent
({
name
:
'BalanceList'
})
</
script
>
<!-- TODO @芋艿:未来实现,等周建的 -->
<
template
>
<div>
余额列表
</div>
</
template
>
<
style
scoped
lang=
"scss"
></
style
>
src/views/member/user/detail/UserAccountInfo.vue
View file @
f545dcc6
...
@@ -2,57 +2,57 @@
...
@@ -2,57 +2,57 @@
<el-descriptions
:column=
"2"
>
<el-descriptions
:column=
"2"
>
<el-descriptions-item>
<el-descriptions-item>
<template
#
label
>
<template
#
label
>
<descriptions-item-label
label=
" 等级 "
icon=
"svg-icon:member_level
"
/>
<descriptions-item-label
icon=
"svg-icon:member_level"
label=
" 等级
"
/>
</
template
>
</
template
>
{{ user.levelName || '无' }}
{{ user.levelName || '无' }}
</el-descriptions-item>
</el-descriptions-item>
<el-descriptions-item>
<el-descriptions-item>
<
template
#
label
>
<
template
#
label
>
<descriptions-item-label
label=
" 成长值 "
icon=
"ep:suitcase
"
/>
<descriptions-item-label
icon=
"ep:suitcase"
label=
" 成长值
"
/>
</
template
>
</
template
>
{{ user.experience || 0 }}
{{ user.experience || 0 }}
</el-descriptions-item>
</el-descriptions-item>
<el-descriptions-item>
<el-descriptions-item>
<
template
#
label
>
<
template
#
label
>
<descriptions-item-label
label=
" 当前积分 "
icon=
"ep:coin
"
/>
<descriptions-item-label
icon=
"ep:coin"
label=
" 当前积分
"
/>
</
template
>
</
template
>
{{ user.point || 0 }}
{{ user.point || 0 }}
</el-descriptions-item>
</el-descriptions-item>
<el-descriptions-item>
<el-descriptions-item>
<
template
#
label
>
<
template
#
label
>
<descriptions-item-label
label=
" 总积分 "
icon=
"ep:coin
"
/>
<descriptions-item-label
icon=
"ep:coin"
label=
" 总积分
"
/>
</
template
>
</
template
>
{{ user.totalPoint || 0 }}
{{ user.totalPoint || 0 }}
</el-descriptions-item>
</el-descriptions-item>
<el-descriptions-item>
<el-descriptions-item>
<
template
#
label
>
<
template
#
label
>
<descriptions-item-label
label=
" 当前余额 "
icon=
"svg-icon:member_balance
"
/>
<descriptions-item-label
icon=
"svg-icon:member_balance"
label=
" 当前余额
"
/>
</
template
>
</
template
>
{{ fenToYuan(wallet.balance || 0) }}
{{ fenToYuan(wallet.balance || 0) }}
</el-descriptions-item>
</el-descriptions-item>
<el-descriptions-item>
<el-descriptions-item>
<
template
#
label
>
<
template
#
label
>
<descriptions-item-label
label=
" 支出金额 "
icon=
"svg-icon:member_expenditure_balance
"
/>
<descriptions-item-label
icon=
"svg-icon:member_expenditure_balance"
label=
" 支出金额
"
/>
</
template
>
</
template
>
{{ fenToYuan(wallet.totalExpense || 0) }}
{{ fenToYuan(wallet.totalExpense || 0) }}
</el-descriptions-item>
</el-descriptions-item>
<el-descriptions-item>
<el-descriptions-item>
<
template
#
label
>
<
template
#
label
>
<descriptions-item-label
label=
" 充值金额 "
icon=
"svg-icon:member_recharge_balance
"
/>
<descriptions-item-label
icon=
"svg-icon:member_recharge_balance"
label=
" 充值金额
"
/>
</
template
>
</
template
>
{{ fenToYuan(wallet.totalRecharge || 0) }}
{{ fenToYuan(wallet.totalRecharge || 0) }}
</el-descriptions-item>
</el-descriptions-item>
</el-descriptions>
</el-descriptions>
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
lang=
"ts"
setup
>
import
{
DescriptionsItemLabel
}
from
'@/components/Descriptions'
import
{
DescriptionsItemLabel
}
from
'@/components/Descriptions'
import
*
as
UserApi
from
'@/api/member/user'
import
*
as
UserApi
from
'@/api/member/user'
import
*
as
WalletApi
from
'@/api/pay/wallet/balance'
import
*
as
WalletApi
from
'@/api/pay/wallet/balance'
import
{
UserTypeEnum
}
from
'@/utils/constants'
import
{
fenToYuan
}
from
'@/utils'
import
{
fenToYuan
}
from
'@/utils'
const
props
=
defineProps
<
{
user
:
UserApi
.
UserVO
;
wallet
:
WalletApi
.
WalletVO
}
>
()
// 用户信息
defineProps
<
{
user
:
UserApi
.
UserVO
;
wallet
:
WalletApi
.
WalletVO
}
>
()
// 用户信息
</
script
>
</
script
>
<
style
scoped
lang=
"scss"
>
<
style
lang=
"scss"
scoped
>
.cell-item
{
.cell-item
{
display
:
inline
;
display
:
inline
;
}
}
...
...
src/views/member/user/detail/UserAftersaleList.vue
View file @
f545dcc6
...
@@ -133,7 +133,7 @@
...
@@ -133,7 +133,7 @@
</div>
</div>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
align=
"center"
label=
"订单金额"
prop=
"refundPrice"
min-width=
"120
"
>
<el-table-column
align=
"center"
label=
"订单金额"
min-width=
"120"
prop=
"refundPrice
"
>
<
template
#
default=
"scope"
>
<
template
#
default=
"scope"
>
<span>
{{
fenToYuan
(
scope
.
row
.
refundPrice
)
}}
元
</span>
<span>
{{
fenToYuan
(
scope
.
row
.
refundPrice
)
}}
元
</span>
</
template
>
</
template
>
...
@@ -153,7 +153,7 @@
...
@@ -153,7 +153,7 @@
<dict-tag
:type=
"DICT_TYPE.TRADE_AFTER_SALE_WAY"
:value=
"scope.row.way"
/>
<dict-tag
:type=
"DICT_TYPE.TRADE_AFTER_SALE_WAY"
:value=
"scope.row.way"
/>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
align=
"center"
fixed=
"right"
label=
"操作"
width=
"1
6
0"
>
<el-table-column
align=
"center"
fixed=
"right"
label=
"操作"
width=
"1
2
0"
>
<
template
#
default=
"{ row }"
>
<
template
#
default=
"{ row }"
>
<el-button
link
type=
"primary"
@
click=
"openAfterSaleDetail(row.id)"
>
处理退款
</el-button>
<el-button
link
type=
"primary"
@
click=
"openAfterSaleDetail(row.id)"
>
处理退款
</el-button>
</
template
>
</
template
>
...
@@ -180,7 +180,7 @@ import { fenToYuan } from '@/utils'
...
@@ -180,7 +180,7 @@ import { fenToYuan } from '@/utils'
defineOptions
({
name
:
'UserAfterSaleList'
})
defineOptions
({
name
:
'UserAfterSaleList'
})
const
{
push
}
=
useRouter
()
// 路由跳转
const
{
push
}
=
useRouter
()
// 路由跳转
const
{
userId
}
=
defineProps
<
{
const
props
=
defineProps
<
{
userId
:
number
userId
:
number
}
>
()
}
>
()
const
loading
=
ref
(
true
)
// 列表的加载中
const
loading
=
ref
(
true
)
// 列表的加载中
...
@@ -197,14 +197,14 @@ const queryFormRef = ref() // 搜索的表单
...
@@ -197,14 +197,14 @@ const queryFormRef = ref() // 搜索的表单
const
queryParams
=
ref
({
const
queryParams
=
ref
({
pageNo
:
1
,
pageNo
:
1
,
pageSize
:
10
,
pageSize
:
10
,
userId
,
no
:
null
,
no
:
null
,
status
:
'0'
,
status
:
'0'
,
orderNo
:
null
,
orderNo
:
null
,
spuName
:
null
,
spuName
:
null
,
createTime
:
[],
createTime
:
[],
way
:
null
,
way
:
null
,
type
:
null
type
:
null
,
userId
:
null
})
})
/** 查询列表 */
/** 查询列表 */
...
@@ -217,7 +217,9 @@ const getList = async () => {
...
@@ -217,7 +217,9 @@ const getList = async () => {
delete
data
.
status
delete
data
.
status
}
}
// 执行查询
// 执行查询
// TODO @芋艿:接口需要通过userId进行筛选返回值
if
(
props
.
userId
)
{
data
.
userId
=
props
.
userId
as
any
}
const
res
=
await
AfterSaleApi
.
getAfterSalePage
(
data
)
const
res
=
await
AfterSaleApi
.
getAfterSalePage
(
data
)
list
.
value
=
res
.
list
as
AfterSaleApi
.
TradeAfterSaleVO
[]
list
.
value
=
res
.
list
as
AfterSaleApi
.
TradeAfterSaleVO
[]
total
.
value
=
res
.
total
total
.
value
=
res
.
total
...
@@ -235,13 +237,12 @@ const handleQuery = async () => {
...
@@ -235,13 +237,12 @@ const handleQuery = async () => {
/** 重置按钮操作 */
/** 重置按钮操作 */
const
resetQuery
=
()
=>
{
const
resetQuery
=
()
=>
{
queryFormRef
.
value
?.
resetFields
()
queryFormRef
.
value
?.
resetFields
()
queryParams
.
value
.
userId
=
userId
handleQuery
()
handleQuery
()
}
}
/** tab 切换 */
/** tab 切换 */
const
tabClick
=
async
(
tab
:
TabsPaneContext
)
=>
{
const
tabClick
=
async
(
tab
:
TabsPaneContext
)
=>
{
queryParams
.
value
.
status
=
tab
.
paneName
queryParams
.
value
.
status
=
tab
.
paneName
as
any
await
getList
()
await
getList
()
}
}
...
...
src/views/member/user/detail/UserBalanceList.vue
View file @
f545dcc6
<
template
>
<
template
>
<ContentWrap>
<ContentWrap>
<el-table
v-loading=
"loading"
:data=
"list"
:stripe=
"true"
:show-overflow-tooltip=
"true"
>
<el-table
v-loading=
"loading"
:data=
"list"
:show-overflow-tooltip=
"true"
:stripe=
"true"
>
<el-table-column
label=
"编号"
align=
"center"
prop=
"id"
/>
<el-table-column
align=
"center"
label=
"编号"
prop=
"id"
/>
<el-table-column
label=
"钱包编号"
align=
"center"
prop=
"walletId"
/>
<el-table-column
align=
"center"
label=
"关联业务标题"
prop=
"title"
/>
<el-table-column
label=
"关联业务标题"
align=
"center"
prop=
"title"
/>
<el-table-column
align=
"center"
label=
"交易金额"
prop=
"price"
>
<el-table-column
label=
"交易金额"
align=
"center"
prop=
"price"
>
<template
#
default=
"
{ row }">
{{
fenToYuan
(
row
.
price
)
}}
元
</
template
>
<template
#
default=
"
{ row }">
{{
fenToYuan
(
row
.
price
)
}}
元
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
label=
"钱包余额"
align=
"center
"
prop=
"balance"
>
<el-table-column
align=
"center"
label=
"钱包余额
"
prop=
"balance"
>
<
template
#
default=
"{ row }"
>
{{
fenToYuan
(
row
.
balance
)
}}
元
</
template
>
<
template
#
default=
"{ row }"
>
{{
fenToYuan
(
row
.
balance
)
}}
元
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
<el-table-column
label=
"交易时间
"
:formatter=
"dateFormatter
"
align=
"center"
align=
"center"
label=
"交易时间"
prop=
"createTime"
prop=
"createTime"
:formatter=
"dateFormatter"
width=
"180px"
width=
"180px"
/>
/>
</el-table>
</el-table>
<!-- 分页 -->
<!-- 分页 -->
<Pagination
<Pagination
:total=
"total"
v-model:page=
"queryParams.pageNo"
v-model:limit=
"queryParams.pageSize"
v-model:limit=
"queryParams.pageSize"
v-model:page=
"queryParams.pageNo"
:total=
"total"
@
pagination=
"getList"
@
pagination=
"getList"
/>
/>
</ContentWrap>
</ContentWrap>
...
@@ -32,8 +31,9 @@
...
@@ -32,8 +31,9 @@
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
*
as
WalletTransactionApi
from
'@/api/pay/wallet/transaction'
import
*
as
WalletTransactionApi
from
'@/api/pay/wallet/transaction'
import
{
fenToYuan
}
from
'@/utils'
import
{
fenToYuan
}
from
'@/utils'
defineOptions
({
name
:
'UserBalanceList'
})
defineOptions
({
name
:
'UserBalanceList'
})
const
{
walletId
}:
{
walletId
:
number
}
=
defineProps
({
const
props
=
defineProps
({
walletId
:
{
walletId
:
{
type
:
Number
,
type
:
Number
,
required
:
false
required
:
false
...
@@ -52,7 +52,7 @@ const list = ref([]) // 列表的数据
...
@@ -52,7 +52,7 @@ const list = ref([]) // 列表的数据
const
getList
=
async
()
=>
{
const
getList
=
async
()
=>
{
loading
.
value
=
true
loading
.
value
=
true
try
{
try
{
queryParams
.
walletId
=
walletId
queryParams
.
walletId
=
props
.
walletId
as
any
const
data
=
await
WalletTransactionApi
.
getWalletTransactionPage
(
queryParams
)
const
data
=
await
WalletTransactionApi
.
getWalletTransactionPage
(
queryParams
)
list
.
value
=
data
.
list
list
.
value
=
data
.
list
total
.
value
=
data
.
total
total
.
value
=
data
.
total
...
@@ -65,4 +65,3 @@ onMounted(() => {
...
@@ -65,4 +65,3 @@ onMounted(() => {
getList
()
getList
()
})
})
</
script
>
</
script
>
<
style
scoped
lang=
"scss"
></
style
>
src/views/member/user/detail/index.vue
View file @
f545dcc6
...
@@ -7,7 +7,7 @@
...
@@ -7,7 +7,7 @@
<template
#
header
>
<template
#
header
>
<div
class=
"card-header"
>
<div
class=
"card-header"
>
<CardTitle
title=
"基本信息"
/>
<CardTitle
title=
"基本信息"
/>
<el-button
type=
"primary"
size=
"small"
text
@
click=
"openForm('update')"
>
<el-button
size=
"small"
text
type=
"primary"
@
click=
"openForm('update')"
>
编辑
编辑
</el-button>
</el-button>
</div>
</div>
...
@@ -16,16 +16,16 @@
...
@@ -16,16 +16,16 @@
</el-col>
</el-col>
<!-- 右上角:账户信息 -->
<!-- 右上角:账户信息 -->
<el-col
:span=
"10"
class=
"detail-info-item"
>
<el-col
:span=
"10"
class=
"detail-info-item"
>
<el-card
shadow=
"never"
class=
"h-full
"
>
<el-card
class=
"h-full"
shadow=
"never
"
>
<
template
#
header
>
<
template
#
header
>
<CardTitle
title=
"账户信息"
/>
<CardTitle
title=
"账户信息"
/>
</
template
>
</
template
>
<UserAccountInfo
:user=
"user"
:wallet=
"wallet"
/>
<UserAccountInfo
:user=
"user"
:wallet=
"wallet"
/>
</el-card>
</el-card>
</el-col>
</el-col>
<!-- 下边:账户明细 -->
<!-- 下边:账户明细 -->
<!-- TODO 芋艿:【订单管理】【售后管理】【收藏记录】-->
<!-- TODO 芋艿:【订单管理】【售后管理】【收藏记录】-->
<el-card
header=
"账户明细"
s
tyle=
"width: 100%; margin-top: 20px"
shadow=
"never
"
>
<el-card
header=
"账户明细"
s
hadow=
"never"
style=
"width: 100%; margin-top: 20px
"
>
<
template
#
header
>
<
template
#
header
>
<CardTitle
title=
"账户明细"
/>
<CardTitle
title=
"账户明细"
/>
</
template
>
</
template
>
...
@@ -39,7 +39,6 @@
...
@@ -39,7 +39,6 @@
<el-tab-pane
label=
"成长值"
lazy
>
<el-tab-pane
label=
"成长值"
lazy
>
<UserExperienceRecordList
:user-id=
"id"
/>
<UserExperienceRecordList
:user-id=
"id"
/>
</el-tab-pane>
</el-tab-pane>
<!-- TODO @jason:增加一个余额变化; -->
<el-tab-pane
label=
"余额"
lazy
>
<el-tab-pane
label=
"余额"
lazy
>
<UserBalanceList
:wallet-id=
"wallet.id"
/>
<UserBalanceList
:wallet-id=
"wallet.id"
/>
</el-tab-pane>
</el-tab-pane>
...
@@ -69,7 +68,7 @@
...
@@ -69,7 +68,7 @@
<!-- 表单弹窗:添加/修改 -->
<!-- 表单弹窗:添加/修改 -->
<UserForm
ref=
"formRef"
@
success=
"getUserData(id)"
/>
<UserForm
ref=
"formRef"
@
success=
"getUserData(id)"
/>
</template>
</template>
<
script
setup
lang=
"ts"
>
<
script
lang=
"ts"
setup
>
import
*
as
WalletApi
from
'@/api/pay/wallet/balance'
import
*
as
WalletApi
from
'@/api/pay/wallet/balance'
import
*
as
UserApi
from
'@/api/member/user'
import
*
as
UserApi
from
'@/api/member/user'
import
{
useTagsViewStore
}
from
'@/store/modules/tagsView'
import
{
useTagsViewStore
}
from
'@/store/modules/tagsView'
...
@@ -85,6 +84,7 @@ import UserPointList from './UserPointList.vue'
...
@@ -85,6 +84,7 @@ import UserPointList from './UserPointList.vue'
import
UserSignList
from
'./UserSignList.vue'
import
UserSignList
from
'./UserSignList.vue'
import
UserFavoriteList
from
'./UserFavoriteList.vue'
import
UserFavoriteList
from
'./UserFavoriteList.vue'
import
UserAfterSaleList
from
'./UserAftersaleList.vue'
import
UserAfterSaleList
from
'./UserAftersaleList.vue'
import
UserBalanceList
from
'./UserBalanceList.vue'
import
{
CardTitle
}
from
'@/components/Card/index'
import
{
CardTitle
}
from
'@/components/Card/index'
import
{
ElMessage
}
from
'element-plus'
import
{
ElMessage
}
from
'element-plus'
...
@@ -142,7 +142,7 @@ onMounted(() => {
...
@@ -142,7 +142,7 @@ onMounted(() => {
getUserWallet
()
getUserWallet
()
})
})
</
script
>
</
script
>
<
style
scoped
lang=
"css"
>
<
style
lang=
"css"
scoped
>
.detail-info-item
:first-child
{
.detail-info-item
:first-child
{
padding-left
:
0
!important
;
padding-left
:
0
!important
;
}
}
...
...
src/views/member/user/index.vue
View file @
f545dcc6
...
@@ -172,7 +172,7 @@
...
@@ -172,7 +172,7 @@
v-if=
"checkPermi(['member:user:update-balance'])"
v-if=
"checkPermi(['member:user:update-balance'])"
command=
"handleUpdateBlance"
command=
"handleUpdateBlance"
>
>
修改余额
(WIP)
修改余额
</el-dropdown-item>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown-menu>
</
template
>
</
template
>
...
@@ -196,6 +196,8 @@
...
@@ -196,6 +196,8 @@
<UserLevelUpdateForm
ref=
"updateLevelFormRef"
@
success=
"getList"
/>
<UserLevelUpdateForm
ref=
"updateLevelFormRef"
@
success=
"getList"
/>
<!-- 修改用户积分弹窗 -->
<!-- 修改用户积分弹窗 -->
<UserPointUpdateForm
ref=
"updatePointFormRef"
@
success=
"getList"
/>
<UserPointUpdateForm
ref=
"updatePointFormRef"
@
success=
"getList"
/>
<!-- 修改用户余额弹窗 -->
<UserBalanceUpdateForm
ref=
"UpdateBalanceFormRef"
@
success=
"getList"
/>
<!-- 发送优惠券弹窗 -->
<!-- 发送优惠券弹窗 -->
<CouponSendForm
ref=
"couponSendFormRef"
/>
<CouponSendForm
ref=
"couponSendFormRef"
/>
</template>
</template>
...
@@ -207,8 +209,9 @@ import UserForm from './UserForm.vue'
...
@@ -207,8 +209,9 @@ import UserForm from './UserForm.vue'
import
MemberTagSelect
from
'@/views/member/tag/components/MemberTagSelect.vue'
import
MemberTagSelect
from
'@/views/member/tag/components/MemberTagSelect.vue'
import
MemberLevelSelect
from
'@/views/member/level/components/MemberLevelSelect.vue'
import
MemberLevelSelect
from
'@/views/member/level/components/MemberLevelSelect.vue'
import
MemberGroupSelect
from
'@/views/member/group/components/MemberGroupSelect.vue'
import
MemberGroupSelect
from
'@/views/member/group/components/MemberGroupSelect.vue'
import
UserLevelUpdateForm
from
'./UserLevelUpdateForm.vue'
import
UserLevelUpdateForm
from
'./components/UserLevelUpdateForm.vue'
import
UserPointUpdateForm
from
'./UserPointUpdateForm.vue'
import
UserPointUpdateForm
from
'./components/UserPointUpdateForm.vue'
import
UserBalanceUpdateForm
from
'./components/UserBalanceUpdateForm.vue'
import
{
CouponSendForm
}
from
'@/views/mall/promotion/coupon/components'
import
{
CouponSendForm
}
from
'@/views/mall/promotion/coupon/components'
import
{
checkPermi
}
from
'@/utils/permission'
import
{
checkPermi
}
from
'@/utils/permission'
...
@@ -233,6 +236,7 @@ const queryParams = reactive({
...
@@ -233,6 +236,7 @@ const queryParams = reactive({
const
queryFormRef
=
ref
()
// 搜索的表单
const
queryFormRef
=
ref
()
// 搜索的表单
const
updateLevelFormRef
=
ref
()
// 修改会员等级表单
const
updateLevelFormRef
=
ref
()
// 修改会员等级表单
const
updatePointFormRef
=
ref
()
// 修改会员积分表单
const
updatePointFormRef
=
ref
()
// 修改会员积分表单
const
UpdateBalanceFormRef
=
ref
()
// 修改用户余额表单
const
selectedIds
=
ref
<
number
[]
>
([])
// 表格的选中 ID 数组
const
selectedIds
=
ref
<
number
[]
>
([])
// 表格的选中 ID 数组
/** 查询列表 */
/** 查询列表 */
...
@@ -299,7 +303,7 @@ const handleCommand = (command: string, row: UserApi.UserVO) => {
...
@@ -299,7 +303,7 @@ const handleCommand = (command: string, row: UserApi.UserVO) => {
updatePointFormRef
.
value
.
open
(
row
.
id
)
updatePointFormRef
.
value
.
open
(
row
.
id
)
break
break
case
'handleUpdateBlance'
:
case
'handleUpdateBlance'
:
// todo @jason:增加一个【修改余额】
UpdateBalanceFormRef
.
value
.
open
(
row
.
id
)
break
break
default
:
default
:
break
break
...
...
src/views/pay/app/components/channel/AlipayChannelForm.vue
View file @
f545dcc6
<
template
>
<
template
>
<div>
<div>
<Dialog
v-model=
"dialogVisible"
:title=
"dialogTitle"
@
closed=
"close"
width=
"830px
"
>
<Dialog
v-model=
"dialogVisible"
:title=
"dialogTitle"
width=
"830px"
@
closed=
"close
"
>
<el-form
<el-form
ref=
"formRef"
ref=
"formRef"
v-loading=
"formLoading"
:model=
"formData"
:model=
"formData"
:rules=
"formRules"
:rules=
"formRules"
label-width=
"100px"
label-width=
"100px"
v-loading=
"formLoading"
>
>
<el-form-item
label
-width=
"180px"
label=
"渠道费率
"
prop=
"feeRate"
>
<el-form-item
label
=
"渠道费率"
label-width=
"180px
"
prop=
"feeRate"
>
<el-input
v-model=
"formData.feeRate"
placeholder=
"请输入渠道费率"
clearable
>
<el-input
v-model=
"formData.feeRate"
clearable
placeholder=
"请输入渠道费率"
>
<template
#
append
>
%
</
template
>
<template
#
append
>
%
</
template
>
</el-input>
</el-input>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"开放平台 APPID
"
prop=
"config.appId"
>
<el-form-item
label
=
"开放平台 APPID"
label-width=
"180px
"
prop=
"config.appId"
>
<el-input
v-model=
"formData.config.appId"
placeholder=
"请输入开放平台 APPID"
clearable
/>
<el-input
v-model=
"formData.config.appId"
clearable
placeholder=
"请输入开放平台 APPID"
/>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"渠道状态
"
prop=
"status"
>
<el-form-item
label
=
"渠道状态"
label-width=
"180px
"
prop=
"status"
>
<el-radio-group
v-model=
"formData.status"
>
<el-radio-group
v-model=
"formData.status"
>
<el-radio
<el-radio
v-for=
"dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
v-for=
"dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
...
@@ -27,7 +27,7 @@
...
@@ -27,7 +27,7 @@
</el-radio>
</el-radio>
</el-radio-group>
</el-radio-group>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"网关地址
"
prop=
"config.serverUrl"
>
<el-form-item
label
=
"网关地址"
label-width=
"180px
"
prop=
"config.serverUrl"
>
<el-radio-group
v-model=
"formData.config.serverUrl"
>
<el-radio-group
v-model=
"formData.config.serverUrl"
>
<el-radio
value=
"https://openapi.alipay.com/gateway.do"
>
线上环境
</el-radio>
<el-radio
value=
"https://openapi.alipay.com/gateway.do"
>
线上环境
</el-radio>
<el-radio
value=
"https://openapi-sandbox.dl.alipaydev.com/gateway.do"
>
<el-radio
value=
"https://openapi-sandbox.dl.alipaydev.com/gateway.do"
>
...
@@ -35,145 +35,148 @@
...
@@ -35,145 +35,148 @@
</el-radio>
</el-radio>
</el-radio-group>
</el-radio-group>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"算法类型
"
prop=
"config.signType"
>
<el-form-item
label
=
"算法类型"
label-width=
"180px
"
prop=
"config.signType"
>
<el-radio-group
v-model=
"formData.config.signType"
>
<el-radio-group
v-model=
"formData.config.signType"
>
<el-radio
key=
"RSA2"
value=
"RSA2"
>
RSA2
</el-radio>
<el-radio
key=
"RSA2"
value=
"RSA2"
>
RSA2
</el-radio>
</el-radio-group>
</el-radio-group>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"公钥类型
"
prop=
"config.mode"
>
<el-form-item
label
=
"公钥类型"
label-width=
"180px
"
prop=
"config.mode"
>
<el-radio-group
v-model=
"formData.config.mode"
>
<el-radio-group
v-model=
"formData.config.mode"
>
<el-radio
key=
"公钥模式"
:value=
"1"
>
公钥模式
</el-radio>
<el-radio
key=
"公钥模式"
:value=
"1"
>
公钥模式
</el-radio>
<el-radio
key=
"证书模式"
:value=
"2"
>
证书模式
</el-radio>
<el-radio
key=
"证书模式"
:value=
"2"
>
证书模式
</el-radio>
</el-radio-group>
</el-radio-group>
</el-form-item>
</el-form-item>
<div
v-if=
"formData.config.mode === 1"
>
<div
v-if=
"formData.config.mode === 1"
>
<el-form-item
label
-width=
"180px"
label=
"应用私钥
"
prop=
"config.privateKey"
>
<el-form-item
label
=
"应用私钥"
label-width=
"180px
"
prop=
"config.privateKey"
>
<el-input
<el-input
type=
"textarea"
:autosize=
"{ minRows: 8, maxRows: 8 }"
v-model=
"formData.config.privateKey"
v-model=
"formData.config.privateKey"
placeholder=
"请输入应用私钥"
:autosize=
"{ minRows: 8, maxRows: 8 }"
clearable
:style=
"{ width: '100%' }"
:style=
"{ width: '100%' }"
clearable
placeholder=
"请输入应用私钥"
type=
"textarea"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"支付宝公钥
"
prop=
"config.alipayPublicKey"
>
<el-form-item
label
=
"支付宝公钥"
label-width=
"180px
"
prop=
"config.alipayPublicKey"
>
<el-input
<el-input
type=
"textarea"
:autosize=
"{ minRows: 8, maxRows: 8 }"
v-model=
"formData.config.alipayPublicKey"
v-model=
"formData.config.alipayPublicKey"
placeholder=
"请输入支付宝公钥"
:autosize=
"{ minRows: 8, maxRows: 8 }"
clearable
:style=
"{ width: '100%' }"
:style=
"{ width: '100%' }"
clearable
placeholder=
"请输入支付宝公钥"
type=
"textarea"
/>
/>
</el-form-item>
</el-form-item>
</div>
</div>
<div
v-if=
"formData.config.mode === 2"
>
<div
v-if=
"formData.config.mode === 2"
>
<el-form-item
label
-width=
"180px"
label=
"应用私钥
"
prop=
"config.privateKey"
>
<el-form-item
label
=
"应用私钥"
label-width=
"180px
"
prop=
"config.privateKey"
>
<el-input
<el-input
type=
"textarea"
:autosize=
"{ minRows: 8, maxRows: 8 }"
v-model=
"formData.config.privateKey"
v-model=
"formData.config.privateKey"
placeholder=
"请输入应用私钥"
:autosize=
"{ minRows: 8, maxRows: 8 }"
clearable
:style=
"{ width: '100%' }"
:style=
"{ width: '100%' }"
clearable
placeholder=
"请输入应用私钥"
type=
"textarea"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"商户公钥应用证书
"
prop=
"config.appCertContent"
>
<el-form-item
label
=
"商户公钥应用证书"
label-width=
"180px
"
prop=
"config.appCertContent"
>
<el-input
<el-input
v-model=
"formData.config.appCertContent"
v-model=
"formData.config.appCertContent"
type=
"textarea"
placeholder=
"请上传商户公钥应用证书"
readonly
:autosize=
"{ minRows: 8, maxRows: 8 }"
:autosize=
"{ minRows: 8, maxRows: 8 }"
:style=
"{ width: '100%' }"
:style=
"{ width: '100%' }"
placeholder=
"请上传商户公钥应用证书"
readonly
type=
"textarea"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"
"
>
<el-form-item
label
=
""
label-width=
"180px
"
>
<el-upload
<el-upload
action=
""
ref=
"privateKeyContentFile"
ref=
"privateKeyContentFile"
:limit=
"1"
:accept=
"fileAccept"
:accept=
"fileAccept"
:http-request=
"appCertUpload"
:before-upload=
"fileBeforeUpload"
:before-upload=
"fileBeforeUpload"
:http-request=
"appCertUpload"
:limit=
"1"
action=
""
>
>
<el-button
type=
"primary"
>
<el-button
type=
"primary"
>
<Icon
icon=
"ep:upload"
class=
"mr-5px"
/>
点击上传
<Icon
class=
"mr-5px"
icon=
"ep:upload"
/>
点击上传
</el-button>
</el-button>
</el-upload>
</el-upload>
</el-form-item>
</el-form-item>
<el-form-item
<el-form-item
label-width=
"180px"
label=
"支付宝公钥证书"
label=
"支付宝公钥证书"
label-width=
"180px"
prop=
"config.alipayPublicCertContent"
prop=
"config.alipayPublicCertContent"
>
>
<el-input
<el-input
v-model=
"formData.config.alipayPublicCertContent"
v-model=
"formData.config.alipayPublicCertContent"
type=
"textarea"
placeholder=
"请上传支付宝公钥证书"
readonly
:autosize=
"{ minRows: 8, maxRows: 8 }"
:autosize=
"{ minRows: 8, maxRows: 8 }"
:style=
"{ width: '100%' }"
:style=
"{ width: '100%' }"
placeholder=
"请上传支付宝公钥证书"
readonly
type=
"textarea"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"
"
>
<el-form-item
label
=
""
label-width=
"180px
"
>
<el-upload
<el-upload
ref=
"privateCertContentFile"
ref=
"privateCertContentFile"
action=
""
:limit=
"1"
:accept=
"fileAccept"
:accept=
"fileAccept"
:before-upload=
"fileBeforeUpload"
:before-upload=
"fileBeforeUpload"
:http-request=
"alipayPublicCertUpload"
:http-request=
"alipayPublicCertUpload"
:limit=
"1"
action=
""
>
>
<el-button
type=
"primary"
>
<el-button
type=
"primary"
>
<Icon
icon=
"ep:upload"
class=
"mr-5px"
/>
点击上传
<Icon
class=
"mr-5px"
icon=
"ep:upload"
/>
点击上传
</el-button>
</el-button>
</el-upload>
</el-upload>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"根证书
"
prop=
"config.rootCertContent"
>
<el-form-item
label
=
"根证书"
label-width=
"180px
"
prop=
"config.rootCertContent"
>
<el-input
<el-input
v-model=
"formData.config.rootCertContent"
v-model=
"formData.config.rootCertContent"
type=
"textarea"
placeholder=
"请上传根证书"
readonly
:autosize=
"{ minRows: 8, maxRows: 8 }"
:autosize=
"{ minRows: 8, maxRows: 8 }"
:style=
"{ width: '100%' }"
:style=
"{ width: '100%' }"
placeholder=
"请上传根证书"
readonly
type=
"textarea"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"
"
>
<el-form-item
label
=
""
label-width=
"180px
"
>
<el-upload
<el-upload
ref=
"privateCertContentFile"
ref=
"privateCertContentFile"
:limit=
"1"
:accept=
"fileAccept"
:accept=
"fileAccept"
action=
""
:before-upload=
"fileBeforeUpload"
:before-upload=
"fileBeforeUpload"
:http-request=
"rootCertUpload"
:http-request=
"rootCertUpload"
:limit=
"1"
action=
""
>
>
<el-button
type=
"primary"
>
<el-button
type=
"primary"
>
<Icon
icon=
"ep:upload"
class=
"mr-5px"
/>
点击上传
<Icon
class=
"mr-5px"
icon=
"ep:upload"
/>
点击上传
</el-button>
</el-button>
</el-upload>
</el-upload>
</el-form-item>
</el-form-item>
</div>
</div>
<el-form-item
label
-width=
"180px"
label=
"接口内容加密方式
"
prop=
"config.encryptType"
>
<el-form-item
label
=
"接口内容加密方式"
label-width=
"180px
"
prop=
"config.encryptType"
>
<el-radio-group
v-model=
"formData.config.encryptType"
>
<el-radio-group
v-model=
"formData.config.encryptType"
>
<el-radio
key=
"NONE"
label=
""
>
无加密
</el-radio>
<el-radio
key=
"NONE"
label=
""
>
无加密
</el-radio>
<el-radio
key=
"AES"
label=
"AES"
>
AES
</el-radio>
<el-radio
key=
"AES"
label=
"AES"
>
AES
</el-radio>
</el-radio-group>
</el-radio-group>
</el-form-item>
</el-form-item>
<div
v-if=
"formData.config.encryptType === 'AES'"
>
<div
v-if=
"formData.config.encryptType === 'AES'"
>
<el-form-item
label
-width=
"180px"
label=
"接口内容加密密钥
"
prop=
"config.encryptKey"
>
<el-form-item
label
=
"接口内容加密密钥"
label-width=
"180px
"
prop=
"config.encryptKey"
>
<el-input
<el-input
v-model=
"formData.config.encryptKey"
v-model=
"formData.config.encryptKey"
placeholder=
"请输入接口内容加密密钥"
clearable
clearable
placeholder=
"请输入接口内容加密密钥"
/>
/>
</el-form-item>
</el-form-item>
</div>
</div>
<el-form-item
label
-width=
"180px"
label=
"备注
"
prop=
"remark"
>
<el-form-item
label
=
"备注"
label-width=
"180px
"
prop=
"remark"
>
<el-input
v-model=
"formData.remark"
:style=
"{ width: '100%' }"
/>
<el-input
v-model=
"formData.remark"
:style=
"{ width: '100%' }"
/>
</el-form-item>
</el-form-item>
</el-form>
</el-form>
...
...
src/views/pay/app/components/channel/MockChannelForm.vue
View file @
f545dcc6
...
@@ -3,12 +3,12 @@
...
@@ -3,12 +3,12 @@
<Dialog
v-model=
"dialogVisible"
:title=
"dialogTitle"
width=
"800px"
>
<Dialog
v-model=
"dialogVisible"
:title=
"dialogTitle"
width=
"800px"
>
<el-form
<el-form
ref=
"formRef"
ref=
"formRef"
v-loading=
"formLoading"
:model=
"formData"
:model=
"formData"
:rules=
"formRules"
:rules=
"formRules"
label-width=
"100px"
label-width=
"100px"
v-loading=
"formLoading"
>
>
<el-form-item
label
-width=
"180px"
label=
"渠道状态
"
prop=
"status"
>
<el-form-item
label
=
"渠道状态"
label-width=
"180px
"
prop=
"status"
>
<el-radio-group
v-model=
"formData.status"
>
<el-radio-group
v-model=
"formData.status"
>
<el-radio
<el-radio
v-for=
"dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
v-for=
"dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
...
@@ -19,7 +19,7 @@
...
@@ -19,7 +19,7 @@
</el-radio>
</el-radio>
</el-radio-group>
</el-radio-group>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"备注
"
prop=
"remark"
>
<el-form-item
label
=
"备注"
label-width=
"180px
"
prop=
"remark"
>
<el-input
v-model=
"formData.remark"
:style=
"
{ width: '100%' }" />
<el-input
v-model=
"formData.remark"
:style=
"
{ width: '100%' }" />
</el-form-item>
</el-form-item>
</el-form>
</el-form>
...
...
src/views/pay/app/components/channel/WalletChannelForm.vue
View file @
f545dcc6
...
@@ -3,12 +3,12 @@
...
@@ -3,12 +3,12 @@
<Dialog
v-model=
"dialogVisible"
:title=
"dialogTitle"
width=
"800px"
>
<Dialog
v-model=
"dialogVisible"
:title=
"dialogTitle"
width=
"800px"
>
<el-form
<el-form
ref=
"formRef"
ref=
"formRef"
v-loading=
"formLoading"
:model=
"formData"
:model=
"formData"
:rules=
"formRules"
:rules=
"formRules"
label-width=
"100px"
label-width=
"100px"
v-loading=
"formLoading"
>
>
<el-form-item
label
-width=
"180px"
label=
"渠道状态
"
prop=
"status"
>
<el-form-item
label
=
"渠道状态"
label-width=
"180px
"
prop=
"status"
>
<el-radio-group
v-model=
"formData.status"
>
<el-radio-group
v-model=
"formData.status"
>
<el-radio
<el-radio
v-for=
"dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
v-for=
"dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
...
@@ -19,7 +19,7 @@
...
@@ -19,7 +19,7 @@
</el-radio>
</el-radio>
</el-radio-group>
</el-radio-group>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"备注
"
prop=
"remark"
>
<el-form-item
label
=
"备注"
label-width=
"180px
"
prop=
"remark"
>
<el-input
v-model=
"formData.remark"
:style=
"
{ width: '100%' }" />
<el-input
v-model=
"formData.remark"
:style=
"
{ width: '100%' }" />
</el-form-item>
</el-form-item>
</el-form>
</el-form>
...
...
src/views/pay/app/components/channel/WeixinChannelForm.vue
View file @
f545dcc6
...
@@ -3,33 +3,33 @@
...
@@ -3,33 +3,33 @@
<Dialog
v-model=
"dialogVisible"
:title=
"dialogTitle"
width=
"800px"
>
<Dialog
v-model=
"dialogVisible"
:title=
"dialogTitle"
width=
"800px"
>
<el-form
<el-form
ref=
"formRef"
ref=
"formRef"
v-loading=
"formLoading"
:model=
"formData"
:model=
"formData"
:rules=
"formRules"
:rules=
"formRules"
label-width=
"120px"
label-width=
"120px"
v-loading=
"formLoading"
>
>
<el-form-item
label
-width=
"180px"
label=
"渠道费率
"
prop=
"feeRate"
>
<el-form-item
label
=
"渠道费率"
label-width=
"180px
"
prop=
"feeRate"
>
<el-input
<el-input
v-model=
"formData.feeRate"
v-model=
"formData.feeRate"
placeholder=
"请输入渠道费率"
clearable
:style=
"
{ width: '100%' }"
:style=
"
{ width: '100%' }"
clearable
placeholder="请输入渠道费率"
>
>
<template
#
append
>
%
</
template
>
<template
#
append
>
%
</
template
>
</el-input>
</el-input>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"微信 APPID
"
prop=
"config.appId"
>
<el-form-item
label
=
"微信 APPID"
label-width=
"180px
"
prop=
"config.appId"
>
<el-input
<el-input
v-model=
"formData.config.appId"
v-model=
"formData.config.appId"
placeholder=
"请输入微信 APPID"
clearable
:style=
"{ width: '100%' }"
:style=
"{ width: '100%' }"
clearable
placeholder=
"请输入微信 APPID"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"商户号
"
prop=
"config.mchId"
>
<el-form-item
label
=
"商户号"
label-width=
"180px
"
prop=
"config.mchId"
>
<el-input
v-model=
"formData.config.mchId"
:style=
"{ width: '100%' }"
/>
<el-input
v-model=
"formData.config.mchId"
:style=
"{ width: '100%' }"
/>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"渠道状态
"
prop=
"status"
>
<el-form-item
label
=
"渠道状态"
label-width=
"180px
"
prop=
"status"
>
<el-radio-group
v-model=
"formData.status"
>
<el-radio-group
v-model=
"formData.status"
>
<el-radio
<el-radio
v-for=
"dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
v-for=
"dict in getDictOptions(DICT_TYPE.COMMON_STATUS)"
...
@@ -40,91 +40,91 @@
...
@@ -40,91 +40,91 @@
</el-radio>
</el-radio>
</el-radio-group>
</el-radio-group>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"API 版本
"
prop=
"config.apiVersion"
>
<el-form-item
label
=
"API 版本"
label-width=
"180px
"
prop=
"config.apiVersion"
>
<el-radio-group
v-model=
"formData.config.apiVersion"
>
<el-radio-group
v-model=
"formData.config.apiVersion"
>
<el-radio
value=
"v2"
>
v2
</el-radio>
<el-radio
value=
"v2"
>
v2
</el-radio>
<el-radio
value=
"v3"
>
v3
</el-radio>
<el-radio
value=
"v3"
>
v3
</el-radio>
</el-radio-group>
</el-radio-group>
</el-form-item>
</el-form-item>
<div
v-if=
"formData.config.apiVersion === 'v2'"
>
<div
v-if=
"formData.config.apiVersion === 'v2'"
>
<el-form-item
label
-width=
"180px"
label=
"商户密钥
"
prop=
"config.mchKey"
>
<el-form-item
label
=
"商户密钥"
label-width=
"180px
"
prop=
"config.mchKey"
>
<el-input
v-model=
"formData.config.mchKey"
placeholder=
"请输入商户密钥"
clearable
/>
<el-input
v-model=
"formData.config.mchKey"
clearable
placeholder=
"请输入商户密钥"
/>
</el-form-item>
</el-form-item>
<el-form-item
<el-form-item
label-width=
"180px"
label=
"apiclient_cert.p12 证书"
label=
"apiclient_cert.p12 证书"
label-width=
"180px"
prop=
"config.keyContent"
prop=
"config.keyContent"
>
>
<el-input
<el-input
v-model=
"formData.config.keyContent"
v-model=
"formData.config.keyContent"
type=
"textarea"
placeholder=
"请上传 apiclient_cert.p12 证书"
readonly
:autosize=
"{ minRows: 8, maxRows: 8 }"
:autosize=
"{ minRows: 8, maxRows: 8 }"
:style=
"{ width: '100%' }"
:style=
"{ width: '100%' }"
placeholder=
"请上传 apiclient_cert.p12 证书"
readonly
type=
"textarea"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"
"
>
<el-form-item
label
=
""
label-width=
"180px
"
>
<el-upload
<el-upload
:before-upload=
"p12FileBeforeUpload"
:http-request=
"keyContentUpload"
:limit=
"1"
:limit=
"1"
accept=
".p12"
accept=
".p12"
action=
""
action=
""
:before-upload=
"p12FileBeforeUpload"
:http-request=
"keyContentUpload"
>
>
<el-button
type=
"primary"
>
<el-button
type=
"primary"
>
<Icon
icon=
"ep:upload"
class=
"mr-5px
"
/>
<Icon
class=
"mr-5px"
icon=
"ep:upload
"
/>
点击上传
点击上传
</el-button>
</el-button>
</el-upload>
</el-upload>
</el-form-item>
</el-form-item>
</div>
</div>
<div
v-if=
"formData.config.apiVersion === 'v3'"
>
<div
v-if=
"formData.config.apiVersion === 'v3'"
>
<el-form-item
label
-width=
"180px"
label=
"API V3 密钥
"
prop=
"config.apiV3Key"
>
<el-form-item
label
=
"API V3 密钥"
label-width=
"180px
"
prop=
"config.apiV3Key"
>
<el-input
<el-input
v-model=
"formData.config.apiV3Key"
v-model=
"formData.config.apiV3Key"
placeholder=
"请输入 API V3 密钥"
clearable
clearable
placeholder=
"请输入 API V3 密钥"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
<el-form-item
label-width=
"180px"
label=
"apiclient_key.pem 证书"
label=
"apiclient_key.pem 证书"
label-width=
"180px"
prop=
"config.privateKeyContent"
prop=
"config.privateKeyContent"
>
>
<el-input
<el-input
v-model=
"formData.config.privateKeyContent"
v-model=
"formData.config.privateKeyContent"
type=
"textarea"
placeholder=
"请上传 apiclient_key.pem 证书"
readonly
:autosize=
"{ minRows: 8, maxRows: 8 }"
:autosize=
"{ minRows: 8, maxRows: 8 }"
:style=
"{ width: '100%' }"
:style=
"{ width: '100%' }"
placeholder=
"请上传 apiclient_key.pem 证书"
readonly
type=
"textarea"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"
"
prop=
"privateKeyContentFile"
>
<el-form-item
label
=
""
label-width=
"180px
"
prop=
"privateKeyContentFile"
>
<el-upload
<el-upload
ref=
"privateKeyContentFile"
ref=
"privateKeyContentFile"
:before-upload=
"pemFileBeforeUpload"
:http-request=
"privateKeyContentUpload"
:limit=
"1"
:limit=
"1"
accept=
".pem"
accept=
".pem"
action=
""
action=
""
:before-upload=
"pemFileBeforeUpload"
:http-request=
"privateKeyContentUpload"
>
>
<el-button
type=
"primary"
>
<el-button
type=
"primary"
>
<Icon
icon=
"ep:upload"
class=
"mr-5px
"
/>
<Icon
class=
"mr-5px"
icon=
"ep:upload
"
/>
点击上传
点击上传
</el-button>
</el-button>
</el-upload>
</el-upload>
</el-form-item>
</el-form-item>
<el-form-item
label
-width=
"180px"
label=
"证书序列号
"
prop=
"config.certSerialNo"
>
<el-form-item
label
=
"证书序列号"
label-width=
"180px
"
prop=
"config.certSerialNo"
>
<el-input
<el-input
v-model=
"formData.config.certSerialNo"
v-model=
"formData.config.certSerialNo"
placeholder=
"请输入证书序列号"
clearable
clearable
placeholder=
"请输入证书序列号"
/>
/>
</el-form-item>
</el-form-item>
</div>
</div>
<el-form-item
label
-width=
"180px"
label=
"备注
"
prop=
"remark"
>
<el-form-item
label
=
"备注"
label-width=
"180px
"
prop=
"remark"
>
<el-input
v-model=
"formData.remark"
:style=
"{ width: '100%' }"
/>
<el-input
v-model=
"formData.remark"
:style=
"{ width: '100%' }"
/>
</el-form-item>
</el-form-item>
</el-form>
</el-form>
...
...
src/views/pay/app/index.vue
View file @
f545dcc6
...
@@ -3,27 +3,27 @@
...
@@ -3,27 +3,27 @@
<!-- 搜索 -->
<!-- 搜索 -->
<ContentWrap>
<ContentWrap>
<el-form
<el-form
class=
"-mb-15px"
:model=
"queryParams"
ref=
"queryFormRef"
ref=
"queryFormRef"
:inline=
"true"
:inline=
"true"
:model=
"queryParams"
class=
"-mb-15px"
label-width=
"68px"
label-width=
"68px"
>
>
<el-form-item
label=
"应用名"
prop=
"name"
>
<el-form-item
label=
"应用名"
prop=
"name"
>
<el-input
<el-input
v-model=
"queryParams.name"
v-model=
"queryParams.name"
placeholder=
"请输入应用名
"
class=
"!w-240px
"
clearable
clearable
placeholder=
"请输入应用名"
@
keyup
.
enter=
"handleQuery"
@
keyup
.
enter=
"handleQuery"
class=
"!w-240px"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item
label=
"开启状态"
prop=
"status"
>
<el-form-item
label=
"开启状态"
prop=
"status"
>
<el-select
<el-select
v-model=
"queryParams.status"
v-model=
"queryParams.status"
placeholder=
"请选择开启状态"
clearable
class=
"!w-240px"
class=
"!w-240px"
clearable
placeholder=
"请选择开启状态"
>
>
<el-option
<el-option
v-for=
"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
v-for=
"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
...
@@ -36,25 +36,25 @@
...
@@ -36,25 +36,25 @@
<el-form-item
label=
"创建时间"
prop=
"createTime"
>
<el-form-item
label=
"创建时间"
prop=
"createTime"
>
<el-date-picker
<el-date-picker
v-model=
"queryParams.createTime"
v-model=
"queryParams.createTime"
value-format=
"YYYY-MM-DD HH:mm:ss"
type=
"daterange"
start-placeholder=
"开始日期"
end-placeholder=
"结束日期"
:default-time=
"[new Date('1 00:00:00'), new Date('1 23:59:59')]"
:default-time=
"[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class=
"!w-240px"
class=
"!w-240px"
end-placeholder=
"结束日期"
start-placeholder=
"开始日期"
type=
"daterange"
value-format=
"YYYY-MM-DD HH:mm:ss"
/>
/>
</el-form-item>
</el-form-item>
<el-form-item>
<el-form-item>
<el-button
@
click=
"handleQuery"
>
<el-button
@
click=
"handleQuery"
>
<Icon
icon=
"ep:search"
class=
"mr-5px
"
/>
<Icon
class=
"mr-5px"
icon=
"ep:search
"
/>
搜索
搜索
</el-button>
</el-button>
<el-button
@
click=
"resetQuery"
>
<el-button
@
click=
"resetQuery"
>
<Icon
icon=
"ep:refresh"
class=
"mr-5px
"
/>
<Icon
class=
"mr-5px"
icon=
"ep:refresh
"
/>
重置
重置
</el-button>
</el-button>
<el-button
type=
"primary"
plain
@
click=
"openForm('create')"
v-hasPermi=
"['pay:app:create']
"
>
<el-button
v-hasPermi=
"['pay:app:create']"
plain
type=
"primary"
@
click=
"openForm('create')
"
>
<Icon
icon=
"ep:plus"
class=
"mr-5px
"
/>
<Icon
class=
"mr-5px"
icon=
"ep:plus
"
/>
新增
新增
</el-button>
</el-button>
</el-form-item>
</el-form-item>
...
@@ -64,9 +64,9 @@
...
@@ -64,9 +64,9 @@
<!-- 列表 -->
<!-- 列表 -->
<ContentWrap>
<ContentWrap>
<el-table
v-loading=
"loading"
:data=
"list"
>
<el-table
v-loading=
"loading"
:data=
"list"
>
<el-table-column
label=
"应用标识"
align=
"center
"
prop=
"appKey"
/>
<el-table-column
align=
"center"
label=
"应用标识
"
prop=
"appKey"
/>
<el-table-column
label=
"应用名"
align=
"center"
prop=
"name"
min-width=
"90
"
/>
<el-table-column
align=
"center"
label=
"应用名"
min-width=
"90"
prop=
"name
"
/>
<el-table-column
label=
"开启状态"
align=
"center
"
prop=
"status"
>
<el-table-column
align=
"center"
label=
"开启状态
"
prop=
"status"
>
<template
#
default=
"scope"
>
<template
#
default=
"scope"
>
<el-switch
<el-switch
v-model=
"scope.row.status"
v-model=
"scope.row.status"
...
@@ -76,28 +76,28 @@
...
@@ -76,28 +76,28 @@
/>
/>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
label=
"支付宝配置"
align=
"center
"
>
<el-table-column
align=
"center"
label=
"支付宝配置
"
>
<el-table-column
<el-table-column
:label=
"channel.name.replace('支付宝', '')"
align=
"center"
v-for=
"channel in alipayChannels"
v-for=
"channel in alipayChannels"
:key=
"channel.code"
:key=
"channel.code"
:label=
"channel.name.replace('支付宝', '')"
align=
"center"
>
>
<
template
#
default=
"scope"
>
<
template
#
default=
"scope"
>
<el-button
<el-button
type=
"success"
v-if=
"isChannelExists(scope.row.channelCodes, channel.code)"
v-if=
"isChannelExists(scope.row.channelCodes, channel.code)"
@
click=
"openChannelForm(scope.row, channel.code)"
circle
circle
size=
"small"
size=
"small"
type=
"success"
@
click=
"openChannelForm(scope.row, channel.code)"
>
>
<Icon
icon=
"ep:check"
/>
<Icon
icon=
"ep:check"
/>
</el-button>
</el-button>
<el-button
<el-button
v-else
v-else
type=
"danger"
circle
circle
size=
"small"
size=
"small"
type=
"danger"
@
click=
"openChannelForm(scope.row, channel.code)"
@
click=
"openChannelForm(scope.row, channel.code)"
>
>
<Icon
icon=
"ep:close"
/>
<Icon
icon=
"ep:close"
/>
...
@@ -105,28 +105,28 @@
...
@@ -105,28 +105,28 @@
</
template
>
</
template
>
</el-table-column>
</el-table-column>
</el-table-column>
</el-table-column>
<el-table-column
label=
"微信配置"
align=
"center
"
>
<el-table-column
align=
"center"
label=
"微信配置
"
>
<el-table-column
<el-table-column
:label=
"channel.name.replace('微信', '')"
align=
"center"
v-for=
"channel in wxChannels"
v-for=
"channel in wxChannels"
:key=
"channel.code"
:key=
"channel.code"
:label=
"channel.name.replace('微信', '')"
align=
"center"
>
>
<
template
#
default=
"scope"
>
<
template
#
default=
"scope"
>
<el-button
<el-button
type=
"success"
v-if=
"isChannelExists(scope.row.channelCodes, channel.code)"
v-if=
"isChannelExists(scope.row.channelCodes, channel.code)"
@
click=
"openChannelForm(scope.row, channel.code)"
circle
circle
size=
"small"
size=
"small"
type=
"success"
@
click=
"openChannelForm(scope.row, channel.code)"
>
>
<Icon
icon=
"ep:check"
/>
<Icon
icon=
"ep:check"
/>
</el-button>
</el-button>
<el-button
<el-button
v-else
v-else
type=
"danger"
circle
circle
size=
"small"
size=
"small"
type=
"danger"
@
click=
"openChannelForm(scope.row, channel.code)"
@
click=
"openChannelForm(scope.row, channel.code)"
>
>
<Icon
icon=
"ep:close"
/>
<Icon
icon=
"ep:close"
/>
...
@@ -134,23 +134,23 @@
...
@@ -134,23 +134,23 @@
</
template
>
</
template
>
</el-table-column>
</el-table-column>
</el-table-column>
</el-table-column>
<el-table-column
label=
"钱包支付配置"
align=
"center
"
>
<el-table-column
align=
"center"
label=
"钱包支付配置
"
>
<el-table-column
:label=
"PayChannelEnum.WALLET.name"
align=
"center"
>
<el-table-column
:label=
"PayChannelEnum.WALLET.name"
align=
"center"
>
<
template
#
default=
"scope"
>
<
template
#
default=
"scope"
>
<el-button
<el-button
type=
"success
"
v-if=
"isChannelExists(scope.row.channelCodes, PayChannelEnum.WALLET.code)
"
circle
circle
size=
"small"
size=
"small"
v-if=
"isChannelExists(scope.row.channelCodes, PayChannelEnum.WALLET.code)
"
type=
"success
"
@
click=
"openChannelForm(scope.row, PayChannelEnum.WALLET.code)"
@
click=
"openChannelForm(scope.row, PayChannelEnum.WALLET.code)"
>
>
<Icon
icon=
"ep:check"
/>
<Icon
icon=
"ep:check"
/>
</el-button>
</el-button>
<el-button
<el-button
v-else
v-else
type=
"danger"
circle
circle
size=
"small"
size=
"small"
type=
"danger"
@
click=
"openChannelForm(scope.row, PayChannelEnum.WALLET.code)"
@
click=
"openChannelForm(scope.row, PayChannelEnum.WALLET.code)"
>
>
<Icon
icon=
"ep:close"
/>
<Icon
icon=
"ep:close"
/>
...
@@ -158,23 +158,23 @@
...
@@ -158,23 +158,23 @@
</
template
>
</
template
>
</el-table-column>
</el-table-column>
</el-table-column>
</el-table-column>
<el-table-column
label=
"模拟支付配置"
align=
"center
"
>
<el-table-column
align=
"center"
label=
"模拟支付配置
"
>
<el-table-column
:label=
"PayChannelEnum.MOCK.name"
align=
"center"
>
<el-table-column
:label=
"PayChannelEnum.MOCK.name"
align=
"center"
>
<
template
#
default=
"scope"
>
<
template
#
default=
"scope"
>
<el-button
<el-button
type=
"success
"
v-if=
"isChannelExists(scope.row.channelCodes, PayChannelEnum.MOCK.code)
"
circle
circle
size=
"small"
size=
"small"
v-if=
"isChannelExists(scope.row.channelCodes, PayChannelEnum.MOCK.code)
"
type=
"success
"
@
click=
"openChannelForm(scope.row, PayChannelEnum.MOCK.code)"
@
click=
"openChannelForm(scope.row, PayChannelEnum.MOCK.code)"
>
>
<Icon
icon=
"ep:check"
/>
<Icon
icon=
"ep:check"
/>
</el-button>
</el-button>
<el-button
<el-button
v-else
v-else
type=
"danger"
circle
circle
size=
"small"
size=
"small"
type=
"danger"
@
click=
"openChannelForm(scope.row, PayChannelEnum.MOCK.code)"
@
click=
"openChannelForm(scope.row, PayChannelEnum.MOCK.code)"
>
>
<Icon
icon=
"ep:close"
/>
<Icon
icon=
"ep:close"
/>
...
@@ -182,21 +182,21 @@
...
@@ -182,21 +182,21 @@
</
template
>
</
template
>
</el-table-column>
</el-table-column>
</el-table-column>
</el-table-column>
<el-table-column
label=
"操作"
align=
"center"
min-width=
"110"
fixed=
"right
"
>
<el-table-column
align=
"center"
fixed=
"right"
label=
"操作"
min-width=
"110
"
>
<
template
#
default=
"scope"
>
<
template
#
default=
"scope"
>
<el-button
<el-button
v-hasPermi=
"['pay:app:update']"
link
link
type=
"primary"
type=
"primary"
@
click=
"openForm('update', scope.row.id)"
@
click=
"openForm('update', scope.row.id)"
v-hasPermi=
"['pay:app:update']"
>
>
编辑
编辑
</el-button>
</el-button>
<el-button
<el-button
v-hasPermi=
"['pay:app:delete']"
link
link
type=
"danger"
type=
"danger"
@
click=
"handleDelete(scope.row.id)"
@
click=
"handleDelete(scope.row.id)"
v-hasPermi=
"['pay:app:delete']"
>
>
删除
删除
</el-button>
</el-button>
...
@@ -205,9 +205,9 @@
...
@@ -205,9 +205,9 @@
</el-table>
</el-table>
<!-- 分页 -->
<!-- 分页 -->
<Pagination
<Pagination
:total=
"total"
v-model:page=
"queryParams.pageNo"
v-model:limit=
"queryParams.pageSize"
v-model:limit=
"queryParams.pageSize"
v-model:page=
"queryParams.pageNo"
:total=
"total"
@
pagination=
"getList"
@
pagination=
"getList"
/>
/>
</ContentWrap>
</ContentWrap>
...
...
src/views/pay/wallet/transaction/WalletTransactionList.vue
View file @
f545dcc6
<
template
>
<
template
>
<ContentWrap>
<ContentWrap>
<el-table
v-loading=
"loading"
:data=
"list"
:s
tripe=
"true"
:show-overflow-tooltip
=
"true"
>
<el-table
v-loading=
"loading"
:data=
"list"
:s
how-overflow-tooltip=
"true"
:stripe
=
"true"
>
<el-table-column
label=
"编号"
align=
"center
"
prop=
"id"
/>
<el-table-column
align=
"center"
label=
"编号
"
prop=
"id"
/>
<el-table-column
label=
"钱包编号"
align=
"center
"
prop=
"walletId"
/>
<el-table-column
align=
"center"
label=
"钱包编号
"
prop=
"walletId"
/>
<el-table-column
label=
"关联业务标题"
align=
"center
"
prop=
"title"
/>
<el-table-column
align=
"center"
label=
"关联业务标题
"
prop=
"title"
/>
<el-table-column
label=
"交易金额"
align=
"center
"
prop=
"price"
>
<el-table-column
align=
"center"
label=
"交易金额
"
prop=
"price"
>
<template
#
default=
"
{ row }">
{{
fenToYuan
(
row
.
price
)
}}
元
</
template
>
<template
#
default=
"
{ row }">
{{
fenToYuan
(
row
.
price
)
}}
元
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
label=
"钱包余额"
align=
"center
"
prop=
"balance"
>
<el-table-column
align=
"center"
label=
"钱包余额
"
prop=
"balance"
>
<
template
#
default=
"{ row }"
>
{{
fenToYuan
(
row
.
balance
)
}}
元
</
template
>
<
template
#
default=
"{ row }"
>
{{
fenToYuan
(
row
.
balance
)
}}
元
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
<el-table-column
label=
"交易时间
"
:formatter=
"dateFormatter
"
align=
"center"
align=
"center"
label=
"交易时间"
prop=
"createTime"
prop=
"createTime"
:formatter=
"dateFormatter"
width=
"180px"
width=
"180px"
/>
/>
</el-table>
</el-table>
<!-- 分页 -->
<!-- 分页 -->
<Pagination
<Pagination
:total=
"total"
v-model:page=
"queryParams.pageNo"
v-model:limit=
"queryParams.pageSize"
v-model:limit=
"queryParams.pageSize"
v-model:page=
"queryParams.pageNo"
:total=
"total"
@
pagination=
"getList"
@
pagination=
"getList"
/>
/>
</ContentWrap>
</ContentWrap>
...
@@ -31,12 +31,18 @@
...
@@ -31,12 +31,18 @@
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
*
as
WalletTransactionApi
from
'@/api/pay/wallet/transaction'
import
*
as
WalletTransactionApi
from
'@/api/pay/wallet/transaction'
import
*
as
WalletApi
from
'@/api/pay/wallet/balance'
import
{
fenToYuan
}
from
'@/utils'
import
{
fenToYuan
}
from
'@/utils'
defineOptions
({
name
:
'WalletTransactionList'
})
defineOptions
({
name
:
'WalletTransactionList'
})
const
{
walletId
}:
{
walletId
:
number
}
=
defineProps
({
const
props
=
defineProps
({
walletId
:
{
walletId
:
{
type
:
Number
,
type
:
Number
,
required
:
false
required
:
false
},
userId
:
{
type
:
Number
,
required
:
false
}
}
})
})
...
@@ -52,7 +58,12 @@ const list = ref([]) // 列表的数据
...
@@ -52,7 +58,12 @@ const list = ref([]) // 列表的数据
const
getList
=
async
()
=>
{
const
getList
=
async
()
=>
{
loading
.
value
=
true
loading
.
value
=
true
try
{
try
{
queryParams
.
walletId
=
walletId
if
(
props
.
userId
)
{
const
wallet
=
await
WalletApi
.
getWallet
({
userId
:
props
.
userId
})
queryParams
.
walletId
=
wallet
.
id
as
any
}
else
{
queryParams
.
walletId
=
props
.
walletId
as
any
}
const
data
=
await
WalletTransactionApi
.
getWalletTransactionPage
(
queryParams
)
const
data
=
await
WalletTransactionApi
.
getWalletTransactionPage
(
queryParams
)
list
.
value
=
data
.
list
list
.
value
=
data
.
list
total
.
value
=
data
.
total
total
.
value
=
data
.
total
...
@@ -65,4 +76,4 @@ onMounted(() => {
...
@@ -65,4 +76,4 @@ onMounted(() => {
getList
()
getList
()
})
})
</
script
>
</
script
>
<
style
scoped
lang=
"scss"
></
style
>
<
style
lang=
"scss"
scoped
></
style
>
src/views/system/menu/index.vue
View file @
f545dcc6
...
@@ -84,9 +84,17 @@
...
@@ -84,9 +84,17 @@
<el-table-column
:show-overflow-tooltip=
"true"
label=
"权限标识"
prop=
"permission"
/>
<el-table-column
:show-overflow-tooltip=
"true"
label=
"权限标识"
prop=
"permission"
/>
<el-table-column
:show-overflow-tooltip=
"true"
label=
"组件路径"
prop=
"component"
/>
<el-table-column
:show-overflow-tooltip=
"true"
label=
"组件路径"
prop=
"component"
/>
<el-table-column
:show-overflow-tooltip=
"true"
label=
"组件名称"
prop=
"componentName"
/>
<el-table-column
:show-overflow-tooltip=
"true"
label=
"组件名称"
prop=
"componentName"
/>
<el-table-column
label=
"状态"
prop=
"status"
width=
"80"
>
<el-table-column
label=
"状态"
prop=
"status"
>
<
template
#
default=
"scope"
>
<
template
#
default=
"scope"
>
<dict-tag
:type=
"DICT_TYPE.COMMON_STATUS"
:value=
"scope.row.status"
/>
<el-switch
class=
"ml-4px"
v-model=
"scope.row.status"
v-hasPermi=
"['system:menu:update']"
:active-value=
"CommonStatusEnum.ENABLE"
:inactive-value=
"CommonStatusEnum.DISABLE"
:loading=
"menuStatusUpdating[scope.row.id]"
@
change=
"(val) => handleStatusChanged(scope.row, val as number)"
/>
</
template
>
</
template
>
</el-table-column>
</el-table-column>
<el-table-column
align=
"center"
label=
"操作"
>
<el-table-column
align=
"center"
label=
"操作"
>
...
@@ -127,8 +135,10 @@
...
@@ -127,8 +135,10 @@
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
handleTree
}
from
'@/utils/tree'
import
{
handleTree
}
from
'@/utils/tree'
import
*
as
MenuApi
from
'@/api/system/menu'
import
*
as
MenuApi
from
'@/api/system/menu'
import
{
MenuVO
}
from
'@/api/system/menu'
import
MenuForm
from
'./MenuForm.vue'
import
MenuForm
from
'./MenuForm.vue'
import
{
CACHE_KEY
,
useCache
}
from
'@/hooks/web/useCache'
import
{
CACHE_KEY
,
useCache
}
from
'@/hooks/web/useCache'
import
{
CommonStatusEnum
}
from
'@/utils/constants'
defineOptions
({
name
:
'SystemMenu'
})
defineOptions
({
name
:
'SystemMenu'
})
...
@@ -208,6 +218,21 @@ const handleDelete = async (id: number) => {
...
@@ -208,6 +218,21 @@ const handleDelete = async (id: number) => {
}
catch
{}
}
catch
{}
}
}
/** 开启/关闭菜单的状态 */
const
menuStatusUpdating
=
ref
({})
// 菜单状态更新中的 menu 映射。key:菜单编号,value:是否更新中
const
handleStatusChanged
=
async
(
menu
:
MenuVO
,
val
:
number
)
=>
{
// 1. 标记 menu.id 更新中
menuStatusUpdating
.
value
[
menu
.
id
]
=
true
try
{
// 2. 发起更新状态
menu
.
status
=
val
await
MenuApi
.
updateMenu
(
menu
)
}
finally
{
// 3. 标记 menu.id 更新完成
menuStatusUpdating
.
value
[
menu
.
id
]
=
false
}
}
/** 初始化 **/
/** 初始化 **/
onMounted
(()
=>
{
onMounted
(()
=>
{
getList
()
getList
()
...
...
vite.config.ts
View file @
f545dcc6
import
{
resolve
}
from
'path'
import
{
resolve
}
from
'path'
import
{
loadEnv
}
from
'vite'
import
type
{
ConfigEnv
,
UserConfig
}
from
'vite'
import
type
{
UserConfig
,
ConfigEnv
}
from
'vite'
import
{
loadEnv
}
from
'vite'
import
{
createVitePlugins
}
from
'./build/vite'
import
{
createVitePlugins
}
from
'./build/vite'
import
{
include
,
exclude
}
from
"./build/vite/optimize"
import
{
exclude
,
include
}
from
"./build/vite/optimize"
// 当前执行node命令时文件夹的地址(工作目录)
// 当前执行node命令时文件夹的地址(工作目录)
const
root
=
process
.
cwd
()
const
root
=
process
.
cwd
()
...
@@ -12,7 +12,7 @@ function pathResolve(dir: string) {
...
@@ -12,7 +12,7 @@ function pathResolve(dir: string) {
}
}
// https://vitejs.dev/config/
// https://vitejs.dev/config/
export
default
({
command
,
mode
}:
ConfigEnv
):
UserConfig
=>
{
export
default
({
command
,
mode
}:
ConfigEnv
):
UserConfig
=>
{
let
env
=
{}
as
any
let
env
=
{}
as
any
const
isBuild
=
command
===
'build'
const
isBuild
=
command
===
'build'
if
(
!
isBuild
)
{
if
(
!
isBuild
)
{
...
@@ -80,6 +80,6 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
...
@@ -80,6 +80,6 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
},
},
},
},
},
},
optimizeDeps
:
{
include
,
exclude
}
optimizeDeps
:
{
include
,
exclude
}
}
}
}
}
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