Merge branch 'master' of http://101.43.112.107:3000/root/allLikeMall into khy-two

This commit is contained in:
khy 2024-11-12 17:48:11 +08:00
commit ef3ea85b66
76 changed files with 2955 additions and 690 deletions

View File

@ -1,29 +1,20 @@
kind: pipeline # 定义对象类型还有secret和signature两种类型
type: docker # 定义流水线类型还有kubernetes、exec、ssh等类型
name: filesystem-drone # 定义流水线名称
clone:
disable: true
steps: # 定义流水线执行步骤,这些步骤将顺序执行
- name: build-java
image: appleboy/drone-ssh # SSH工具镜像
settings:
host: 1.14.205.126 # 远程连接地址
username: root # 远程连接账号
password:
password:
from_secret: ssh_password # 从Secret中读取SSH密码
port: 22 # 远程连接端口
command_timeout: 30m # 远程执行命令超时时间
script:
- echo "build-java......"
- cd /root/allLikeMall
@ -34,4 +25,3 @@ steps: # 定义流水线执行步骤,这些步骤将顺序执行
- cd yudao-server
- chmod +x all.sh
- ./all.sh

View File

@ -56,3 +56,32 @@ export const getDiyTemplateProperty = async (id: number) => {
export const updateDiyTemplateProperty = async (data: DiyTemplateVO) => {
return await request.put({ url: `/promotion/diy-template/update-property`, data })
}
// 设置商品分类接口
export const setDiyProjuctClass = async (id) => {
return await request.get({
url: `/system/dict-data/diy-template-goods?id=` + id
})
}
// 获取商品分类接口
export const getDiyProjuctClass = async () => {
return await request.get({
url: `/system/dict-data/getGoods`
})
}
// 设置主题风格
export const setDiyZtClass = async (id) => {
return await request.get({
url: `/system/dict-data/diy-template-theme?id=` + id
})
}
// 获取主题风格
export const getDiyZtClass = async () => {
return await request.get({
url: `/system/dict-data/getTheme`
})
}

View File

@ -20,6 +20,7 @@ export interface UserVO {
point: number | undefined | null
totalPoint: number | undefined | null
experience: number | null | undefined
groupName: string
}
// 查询会员用户列表
@ -51,3 +52,8 @@ export const updateUserPoint = async (data: any) => {
export const updateUserBalance = async (data: any) => {
return await request.put({ url: `/member/user/update-balance`, data })
}
// 客服查询用户详情
export const getUserInfo = async (id: number) => {
return await request.get({ url: `/member/user/getUserInfo?id=` + id })
}

View File

@ -0,0 +1,56 @@
import request from '@/config/axios'
// 钱包充值 VO
export interface WalletRechargeVO {
id: number // id
walletId: number // 钱包编号
totalPrice: number // 充值实际到账
payPrice: number // 实际支付金额
bonusPrice: number // 钱包赠送金额
packageId: number // 充值套餐编号
payStatus: boolean // 是否支付
payOrderId: number // 支付订单编号
payChannelCode: string // 支付成功的支付渠道
payTime: Date // 订单支付时间
payRefundId: number // 支付退款单编号
refundTotalPrice: number // 退款金额(包含赠送金额)
refundPayPrice: number // 退款支付金额
refundBonusPrice: number // 退款钱包赠送金额
refundTime: Date // 退款时间
refundStatus: number // 退款状态
name : string
avatar: string
}
// 钱包充值 API
export const WalletRechargeApi = {
// 查询钱包充值分页
getWalletRechargePage: async (params: any) => {
return await request.get({ url: `/pay/wallet-recharge/page`, params })
},
// 查询钱包充值详情
getWalletRecharge: async (id: number) => {
return await request.get({ url: `/pay/wallet-recharge/get?id=` + id })
},
// 新增钱包充值
createWalletRecharge: async (data: WalletRechargeVO) => {
return await request.post({ url: `/pay/wallet-recharge/create`, data })
},
// 修改钱包充值
updateWalletRecharge: async (data: WalletRechargeVO) => {
return await request.put({ url: `/pay/wallet-recharge/update`, data })
},
// 删除钱包充值
deleteWalletRecharge: async (id: number) => {
return await request.delete({ url: `/pay/wallet-recharge/delete?id=` + id })
},
// 导出钱包充值 Excel
exportWalletRecharge: async (params) => {
return await request.download({ url: `/pay/wallet-recharge/export-excel`, params })
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -24,7 +24,7 @@
:content="appLink.path" placement="bottom" :show-after="300">
<el-button class="m-b-8px m-r-8px m-l-0px!"
:type="isSameLink(appLink.path, activeAppLink.path) ? 'primary' : 'default'"
:type="appLink.path == activeAppLink.path ? 'primary' : 'default'"
@click="handleAppLinkSelected(appLink)">
{{ appLink.name }}
</el-button>
@ -109,9 +109,14 @@
// APP
const handleAppLinkSelected = (appLink : AppLink) => {
if (!isSameLink(appLink.path, activeAppLink.value.path)) {
activeAppLink.value = appLink
console.log(activeAppLink.value,activeAppLink.value.path,"activeAppLink.value")
if(!appLink.path.includes('/pages/index/page')){
if (!isSameLink(appLink.path, activeAppLink.value.path)) {
activeAppLink.value = appLink
// console.log(activeAppLink.value,activeAppLink.value.path,"activeAppLink.value")
}
}else{
activeAppLink.value.path = appLink.path
console.log(activeAppLink.value.path,"activeAppLink.value.path")
}
switch (appLink.type) {
case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST:

View File

@ -65,11 +65,11 @@ export const APP_LINK_GROUP_LIST = [
name: '商品搜索',
path: '/pages/index/search'
},
{
name: '自定义页面',
path: '/pages/index/page',
type: APP_LINK_TYPE_ENUM.DIY_PAGE_DETAIL
},
// {
// name: '自定义页面',
// path: '/pages/index/page',
// type: APP_LINK_TYPE_ENUM.DIY_PAGE_DETAIL
// },
{
name: '客服',
path: '/pages/chat/index'
@ -78,13 +78,25 @@ export const APP_LINK_GROUP_LIST = [
name: '系统设置',
path: '/pages/public/setting'
},
{
name: '常见问题',
path: '/pages/public/faq'
},
// {
// name: '常见问题',
// path: '/pages/public/faq'
// },
{
name: '积分商城',
path: '/pages/public/faq'
path: '/pages/index/page?id=3'
},
{
name:'我的积分',
path:'/pages/user/wallet/score'
},
{
name:'兑换记录',
path:'/pages/activity/point/exchange_list'
},
{
name:'积分商品列表',
path:'/pages/activity/point/exchange_listall?id=3'
}
]
},
@ -111,11 +123,11 @@ export const APP_LINK_GROUP_LIST = [
path: '/pages/goods/seckill',
type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_SECKILL
},
{
name: '促销列表',
path: '/pages/goods/sales',
type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_SECKILL
},
// {
// name: '促销列表',
// path: '/pages/goods/sales',
// type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_SECKILL
// },
{
name: '门店管理',
path: '/pages/user/goods_details_store/index',
@ -195,11 +207,12 @@ export const APP_LINK_GROUP_LIST = [
{
name: '充值记录',
path: '/pages/pay/recharge-log'
},
{
name: '核销记录',
path: '/pages/pay/recharge-log'
}
// ,
// {
// name: '核销记录',
// path: '/pages/pay/recharge-log'
// }
]
},
{
@ -248,7 +261,19 @@ export const APP_LINK_GROUP_LIST = [
{
name: '会员中心',
path: '/pages/user/user_vip/index'
}
},
{
name:'付费会员',
path:'/pages/user/user_vip/list'
},
{
name:'预约中心',
path:'/pages/subscribe/subscribe'
},
{
name:'预约记录',
path:'pages/reservation_record/reservation_record'
}
]
}
// ,

View File

@ -1,7 +1,7 @@
<template>
<el-scrollbar class="z-1 min-h-30px" wrap-class="w-full" ref="containerRef">
<div
class="new-class flex flex-row text-12px"
class="new-class flex-row text-12px"
:style="{
gap: `${property.space}px`,
width: scrollbarWidth
@ -13,11 +13,13 @@
background: property.bgImg
? `url(${property.bgImg}) 100% center / 100% 100% no-repeat`
: '#fff',
width: `${couponWidth}px`,
width: `150px`,
padding: `10px 10px`,
color: property.textColor
}"
v-for="(coupon, index) in couponList"
:key="index"
style="padding-left: 0px;padding-right: 4px;"
>
<!-- 布局11-->
<!-- <div v-if="property.columns === 1" class="m-l-16px flex flex-row justify-between p-8px">
@ -41,16 +43,17 @@
<!-- 布局22-->
<!-- v-else-if="property.columns === 2"s -->
<div
class="m-l-16px flex flex-row justify-between"
class="flex flex-row justify-between"
style=""
>
<div class="flex flex-col justify-evenly gap-4px">
<div class="flex flex-col justify-evenly gap-4px" style="flex:1;">
<!-- 优惠值 -->
<CouponDiscount :coupon="coupon" />
<div>{{ coupon.name }}</div>
<CouponDiscount :coupon="coupon" style="text-align:center;" />
<div style="text-align:center;">{{ coupon.name }}</div>
</div>
<div class="flex flex-col">
<div class="flex flex-col" style="writing-mode: vertical-rl;">
<div
class="h-full w-20px rounded-20px p-x-2px p-y-8px text-center"
class="h-full w-20px rounded-20px text-center"
:style="{
color: property.button.color,
background: property.button.bgColor
@ -138,8 +141,11 @@ onMounted(() => {
<style scoped lang="scss">
.new-class{
margin-left: 10px;
overflow-x:scroll;
white-space: nowrap;
.box-content{
margin-right:10px;
display:inline-block;
}
}
</style>

View File

@ -3,7 +3,7 @@
<div
v-for="(item, index) in property.list"
:key="index"
class="relative flex flex-col items-center p-b-14px p-t-20px"
class="relative flex flex-col items-center"
:style="{ width: `${100 * (1 / property.column)}%` }"
>
<!-- 右上角角标 -->
@ -14,11 +14,11 @@
>
{{ item.badge.text }}
</span>
<el-image v-if="item.iconUrl" class="h-28px w-28px" :src="item.iconUrl" />
<el-image v-if="item.iconUrl" class="h-40px w-40px" :src="item.iconUrl" />
<span class="m-t-8px h-16px text-12px leading-16px" :style="{ color: item.titleColor }">
{{ item.title }}
</span>
<span class="m-t-6px h-12px text-10px leading-12px" :style="{ color: item.subtitleColor }">
<span v-if="item.subtitle" class="m-t-6px h-12px text-10px leading-12px" :style="{ color: item.subtitleColor }">
{{ item.subtitle }}
</span>
</div>
@ -32,4 +32,9 @@ defineOptions({ name: 'MenuGrid' })
defineProps<{ property: MenuGridProperty }>()
</script>
<style scoped lang="scss"></style>
<style scoped lang="scss">
.items-center{
margin:5px 0;
}
</style>

View File

@ -0,0 +1,80 @@
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
import { cloneDeep } from 'lodash-es'
/** 宫格导航属性 */
export interface MenuGridProperty {
// 列数
column: number
// 导航菜单列表
list: MenuGridItemProperty[]
// 组件样式
style: ComponentStyle
}
/** 宫格导航项目属性 */
export interface MenuGridItemProperty {
// 图标链接
iconUrl: string
// 标题
title: string
// 标题颜色
titleColor: string
// 副标题
subtitle: string
// 副标题颜色
subtitleColor: string
// 链接
url: string
// 角标
badge: {
// 是否显示
show: boolean
// 角标文字
text: string
// 角标文字颜色
textColor: string
// 角标背景颜色
bgColor: string
}
}
export const EMPTY_MENU_GRID_ITEM_PROPERTY = {
title: '标题',
titleColor: '#333',
subtitle: '副标题',
subtitleColor: '#bbb',
badge: {
show: false,
textColor: '#fff',
bgColor: '#FF6000'
}
} as MenuGridItemProperty
import logo from '@/assets/imgs/DiyEditorImges/组件图标-04.png'
// 定义组件
export const component = {
id: 'MenuGridTow',
name: '魔方',
// icon: 'bi:grid-3x3-gap',
icon: logo,
property: {
column: 3,
list: [cloneDeep(EMPTY_MENU_GRID_ITEM_PROPERTY)],
style: {
bgType: 'color',
bgColor: '#fff',
marginBottom: 8,
marginLeft: 8,
marginRight: 8,
padding: 8,
paddingTop: 8,
paddingRight: 8,
paddingBottom: 8,
paddingLeft: 8,
borderRadius: 8,
borderTopLeftRadius: 8,
borderTopRightRadius: 8,
borderBottomRightRadius: 8,
borderBottomLeftRadius: 8
} as ComponentStyle
}
} as DiyComponent<MenuGridProperty>

View File

@ -0,0 +1,201 @@
<template>
<div class='new_czbk'>
<div class="t">
<text class="left-font">超值爆款</text>
<div class="sub">美好生活由此开始</div>
</div>
<div class="new-list">
<!-- <div class="item" @click="sheep.$router.go('/pages/goods/sales', {
activityType: 'recommendBest',
});"> -->
<div class="item" >
<div class="nei">
<div class="l">
<div class="t">今日推荐</div>
<div class="c">店主诚意推荐品质商品</div>
<div class="b">
<!-- GO! -->
<img src="https://zysc.fjptzykj.com:3000/shangcheng/40a7582be7aeb5a6047415ab3a6728439a2798a4752ffcc1c063d66a534aa630.png"
class="img"/>
</div>
</div>
<div class="r">
<img
src="https://zysc.fjptzykj.com:3000/shangcheng/c80faeeeb2d6dadcdbe7e6d1a4caa06869b94301612cdf157414a10559b38267.png"
class="img"/>
</div>
</div>
</div>
<!-- <div class="item" @click="sheep.$router.go('/pages/goods/sales', {
activityType: 'recommendHot',
});"> -->
<div class="item">
<div class="nei">
<div class="l">
<div class="t">热门榜单</div>
<div class="c">店主诚意推荐品质商品</div>
<div class="b">
<!-- GO! -->
<img
src="https://zysc.fjptzykj.com:3000/shangcheng/5c72fc99989bf0a7e59b57f519c76c9a98cb20478384e20b1b934aa8f80cc598.png"
class="img"/>
</div>
</div>
<div class="r">
<img
src="https://zysc.fjptzykj.com:3000/shangcheng/904fd4848fde8025ccc17693662efd5b7bf5ec3f2656ecc7de407c8c46f910b2.png"
class="img"/>
</div>
</div>
</div>
<!-- <div class="item" @click="sheep.$router.go('/pages/goods/sales', {
activityType: 'recommendNew',
});"> -->
<div class="item">
<div class="nei">
<div class="l">
<div class="t">首发新品</div>
<div class="c">新品上架等 你来拿</div>
<div class="b">
<!-- GO! -->
<img
src="https://zysc.fjptzykj.com:3000/shangcheng/ebb2fc0512d1bd6a6d21a2c07f6603bfd2ea6f7e23377eb4cd8118d89d666589.png"
class="img"/>
</div>
</div>
<div class="r">
<img
src="https://zysc.fjptzykj.com:3000/shangcheng/7c71cbf2c757418302ea9ef2952893c24e1d46c4cc35a821f56be0f315bcabb0.png"
class="img"/>
</div>
</div>
</div>
<!-- <div class="item" @click="sheep.$router.go('/pages/goods/sales', {
activityType: 'recommendGood',
});"> -->
<div class="item">
<div class="nei">
<div class="l">
<div class="t">促销单品</div>
<div class="c">综合评选好 产品</div>
<div class="b">
<!-- GO! -->
<img
src="https://zysc.fjptzykj.com:3000/shangcheng/aac8baa7a6feb9ee3459d1cf75ba9a7e8bacb5d93c88ac95c1b0ecd29b847f96.png"
class="img"/>
</div>
</div>
<div class="r">
<img
src="https://zysc.fjptzykj.com:3000/shangcheng/8a61c35d82702c61f97ef3db43a8d6755b2eca3d78a22dbaa55851e815e96a23.png"
class="img" />
</div>
</div>
</div>
<div style="clear:both;"></div>
</div>
</div>
</template>
<script setup lang="ts">
import { MenuGridProperty } from './config'
/** 宫格导航 */
defineOptions({ name: 'MenuGridTow' })
defineProps<{ property: MenuGridProperty }>()
</script>
<style scoped lang="scss">
.new_czbk {
width: 100%;
background: rgba(255, 229, 227);
border-radius: 5px;
margin: 0 auto;
padding: 5px 0;
// margin-top: 10px;
padding-bottom: 10px;
padding-top: 10px;
&>.t {
display: flex;
align-items: center;
padding:0 10px;
.left-font {
color: rgba(252, 60, 62);
font-size: 17px;
font-weight: 700;
}
.sub {
background: rgba(248, 79, 43);
color: white;
border-radius: 20px 0px 20px 0px;
padding: 0 13px;
margin-left: 10px;
font-size: 13px;
}
}
.new-list {
// width: 100%;
margin-right:10px;
.item {
float: left;
width: 50%;
.nei {
margin: 10px;
margin-right:0;
margin-bottom: 0;
background: white;
border-radius: 10px;
padding: 8px 10px;
display: flex;
.l {
width: 50%;
.t {
// font-size: 17px;
}
.c {
color: rgba(153, 153, 153);
font-size: 12px;
margin: 3px 0;
}
.b {
// display: flex;
// align-items: center;
// justify-content: space-around;
// background: #329cff;
// margin: 0 3px;
// border-radius: 15px;
// color: white;
// font-weight: 700;
// padding: 0px 9px;
// font-style: oblique;
.img {
width: 76%;
height: 20px;
}
}
}
.r {
width: 49%;
.img {
width: 100%;
height: 100%;
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,66 @@
<template>
<ComponentContainerProperty v-model="formData.style">
<!-- 表单 -->
<el-form label-width="80px" :model="formData" class="m-t-8px">
<el-form-item label="每行数量" prop="column">
<el-radio-group v-model="formData.column">
<el-radio :label="3">3</el-radio>
<el-radio :label="4">4</el-radio>
<el-radio :label="5">5</el-radio>
</el-radio-group>
</el-form-item>
<el-card header="菜单设置" class="property-group" shadow="never">
<Draggable v-model="formData.list" :empty-item="EMPTY_MENU_GRID_ITEM_PROPERTY">
<template #default="{ element }">
<el-form-item label="图标" prop="iconUrl">
<UploadImg v-model="element.iconUrl" height="80px" width="80px">
<template #tip> 建议尺寸44 * 44 </template>
</UploadImg>
</el-form-item>
<el-form-item label="标题" prop="title">
<InputWithColor v-model="element.title" v-model:color="element.titleColor" />
</el-form-item>
<el-form-item label="副标题" prop="subtitle">
<InputWithColor v-model="element.subtitle" v-model:color="element.subtitleColor" />
</el-form-item>
<el-form-item label="链接" prop="url">
<AppLinkInput v-model="element.url" />
</el-form-item>
<el-form-item label="显示角标" prop="badge.show">
<el-switch v-model="element.badge.show" />
</el-form-item>
<template v-if="element.badge.show">
<el-form-item label="角标内容" prop="badge.text">
<InputWithColor
v-model="element.badge.text"
v-model:color="element.badge.textColor"
/>
</el-form-item>
<el-form-item label="背景颜色" prop="badge.bgColor">
<ColorInput v-model="element.badge.bgColor" />
</el-form-item>
</template>
</template>
</Draggable>
</el-card>
</el-form>
</ComponentContainerProperty>
</template>
<script setup lang="ts">
import { usePropertyForm } from '@/components/DiyEditor/util'
import {
EMPTY_MENU_GRID_ITEM_PROPERTY,
MenuGridProperty
} from '@/components/DiyEditor/components/mobile/MenuGrid/config'
/** 宫格导航属性面板 */
defineOptions({ name: 'MenuGridProperty' })
const props = defineProps<{ modelValue: MenuGridProperty }>()
const emit = defineEmits(['update:modelValue'])
const { formData } = usePropertyForm(props.modelValue, emit)
</script>
<style scoped lang="scss"></style>

View File

@ -10,7 +10,7 @@
<div class="h-24px truncate leading-24px">{{ item.text }}</div>
</el-carousel-item>
</el-carousel>
<Icon icon="ep:arrow-right" />
<!-- <Icon icon="ep:arrow-right" /> -->
</div>
</template>

View File

@ -1,5 +1,23 @@
<template>
<div :class="`box-content min-h-30px w-full flex flex-row flex-wrap`" ref="containerRef">
<view class="new-fenlei">
<view class="list on">
<view class="t">首页新品</view>
<view class="b">最新出炉</view>
</view>
<view class="list">
<view class="t">精品推荐</view>
<view class="b">猜你喜欢</view>
</view>
<view class="list">
<view class="t">热门榜单</view>
<view class="b">好评如云</view>
</view>
<view class="list">
<view class="t">促销单品</view>
<view class="b">多买多销</view>
</view>
</view>
<div
class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
:style="{
@ -164,4 +182,44 @@ const calculateWidth = () => {
}
</script>
<style scoped lang="scss"></style>
<style scoped lang="scss">
.new-fenlei {
width: 100%;
display: flex;
margin:10px 0;
justify-content: space-around;
.list {
width: 100%;
display:flex;
flex-wrap:wrap;
&.on{
.t {
color: #e93422;
}
.b {
background: #e93422;
color:white;
}
}
.t {
width:100%;
text-align: center;
font-size: 16px;
}
.b {
// background: rgba(255, 102, 7);
text-align: center;
color: rgba(153,153,153);
border-radius: 15px;
width: 63%;
margin: 0 auto;
font-size: 12px;
margin-top:5px;
}
}
}
</style>

View File

@ -27,8 +27,8 @@
:style="{
gridGap: `${property.space}px`,
gridTemplateColumns,
width: scrollbarWidth
}"
style="padding:0 10px;padding-bottom: 8px;"
>
<!-- 商品 -->
<div
@ -53,7 +53,7 @@
<el-image fit="cover" :src="spu.picUrl" :style="{ width: imageSize, height: imageSize }" />
<div
:class="[
'flex flex-col gap-8px p-8px box-border',
'flex flex-col p-8px box-border',
{
'w-[calc(100%-64px)]': columns === 2,
'w-full': columns === 3
@ -79,6 +79,7 @@
</span>
</div>
</div>
<view class="sss">参与拼团</view>
</div>
</div>
</el-scrollbar>
@ -145,9 +146,18 @@ onMounted(() => {
</script>
<style scoped lang="scss">
.sss {
width: 100%;
// margin-top: 10px;
padding: 3px;
font-size: 13px;
background: #e93422;
text-align: center;
color: white;
border-radius: 0px 0px 5px 5px;
}
.new-class{
padding: 10px;
padding:0 0;
// width:42%;
}

View File

@ -10,7 +10,12 @@
src="https://zysc.fjptzykj.com:3000/shangcheng/cb995c399d784c08e27d8f56b0a63ace2d13af3a1ee6aba5a2da71868dc4cf00.png" />
<span class="new-text">限时秒杀</span>
</div>
<span class="text-16px" style="color: rgb(187, 187, 187);">已有99人购买</span>
<div style="color: rgba(255, 51, 35, 1); font-size:15px;" class="title-text">
<span class="time">05</span>:
<span class="time">00</span>:
<span class="time">00</span>
</div>
</div>
<div class="item-center flex flex-row justify-center gap-4px">
<span class="text-12px" style="color: rgb(187, 187, 187);">查看更多</span>
@ -23,10 +28,8 @@
<!-- 商品网格 -->
<!-- gridGap: `${property.space}px`, -->
<div class="new-main" :style="{
gridTemplateColumns,
width: scrollbarWidth
}">
gridTemplateColumns,
}" style="padding:0 10px;">
<!-- 商品 -->
<!--
:style="{
@ -44,19 +47,14 @@
</div>
<!-- 商品封面图 -->
<el-image fit="cover" :src="spu.picUrl" :style="{ width: imageSize, height: imageSize }" />
<div :class="[
'flex flex-col gap-8px p-8px box-border',
{
'w-[calc(100%-64px)]': columns === 2,
'w-full': columns === 3
}
]">
<div>
<!-- 商品名称 -->
<div v-if="property.fields.name.show" class="truncate text-12px"
:style="{ color: property.fields.name.color }">
:style="{ color: property.fields.name.color }" style="margin: 5px 0;">
{{ spu.name }}
</div>
<div>
<div style="display: flex;">
<div class="fff"></div>
<!-- 商品价格 -->
<span v-if="property.fields.price.show" class="text-12px"
:style="{ color: property.fields.price.color }">
@ -129,6 +127,24 @@
</script>
<style scoped lang="scss">
.fff {
padding: 1px 3px;
font-size: 8px;
background: #e93422;
color: white;
border-radius: 2px;
margin-right: 2px;
}
.time {
width: 20px;
height: 20px;
background: rgba(255, 237, 238, 1);
color: rgba(255, 51, 35, 1);
text-align: center;
padding: 0 3px;
}
:deep(.el-scrollbar__view) {
background-color: white;
border-radius: 12px;
@ -136,28 +152,36 @@
.new-class {
width: 33%;
padding: 10px;
border-radius: 8px 8px 8px 0px;
padding-bottom: 10px;
// padding: 10px;
// width:42%;
}
.new-main {
display: flex;
gap: 8px;
grid-template-columns: repeat(3, auto);
padding: 0px 10px 8px;
padding-bottom: 10px;
}
:deep(.el-image) {
width: 100% !important;
}
// :deep(.el-image) {
// width: 100% !important;
// }
.wh {
position: relative;
padding-right: 10px;
display: flex;
align-items: center;
.new-text1{
width:30px;
.new-text1 {
width: 30px;
}
.new-text{
font-weight: 700;
.new-text {
font-weight: 700;
}
}

View File

@ -69,10 +69,10 @@
</draggable>
</el-scrollbar>
<!-- 手机底部导航 -->
<div v-if="showTabBar" :class="['editor-design-bottom', 'component', 'cursor-pointer!']">
<!-- <div v-if="showTabBar" :class="['editor-design-bottom', 'component', 'cursor-pointer!']">
<ComponentContainer :component="tabBarComponent" :show-toolbar="false"
:active="selectedComponent?.id === tabBarComponent.id" @click="handleTabBarSelected" />
</div>
</div> -->
<!-- 固定布局的组件 操作按钮区 -->
<div class="fixed-component-action-group">
<el-tag v-if="showPageConfig" size="large"

View File

@ -118,7 +118,8 @@ export const PAGE_LIBS = [
'MenuGrid',
'MenuList',
'Popover',
'FloatingActionButton'
'FloatingActionButton',
"MenuGridTow"
]
},
{

View File

@ -54,7 +54,7 @@ export default defineComponent({
onClick={handleClickOutside}
></div>
) : undefined}
{renderLayout()}
<Backtop></Backtop>

View File

@ -35,88 +35,120 @@ const mobile = computed(() => appStore.getMobile)
// 固定菜单
const fixedMenu = computed(() => appStore.getFixedMenu)
import { useRoute } from 'vue-router'
export const useRenderLayout = () => {
const renderClassic = () => {
return (
<>
<div
class={[
'absolute top-0 left-0 h-full layout-border__right',
{ '!fixed z-3000': mobile.value }
]}
>
{logo.value ? (
<Logo
class={[
'bg-[var(--left-menu-bg-color)] relative',
{
'!pl-0': mobile.value && collapse.value,
'w-[var(--left-menu-min-width)]': appStore.getCollapse,
'w-[var(--left-menu-max-width)]': !appStore.getCollapse
}
]}
style="transition: all var(--transition-time-02);"
></Logo>
) : undefined}
<Menu class={[{ '!h-[calc(100%-var(--logo-height))]': logo.value }]}></Menu>
</div>
<div
class={[
`${prefixCls}-content`,
'absolute top-0 h-[100%]',
{
'w-[calc(100%-var(--left-menu-min-width))] left-[var(--left-menu-min-width)]':
collapse.value && !mobile.value && !mobile.value,
'w-[calc(100%-var(--left-menu-max-width))] left-[var(--left-menu-max-width)]':
!collapse.value && !mobile.value && !mobile.value,
'fixed !w-full !left-0': mobile.value
}
]}
style="transition: all var(--transition-time-02);"
>
<ElScrollbar
v-loading={pageLoading.value}
class={[
`${prefixCls}-content-scrollbar`,
{
'!h-[calc(100%-var(--top-tool-height)-var(--tags-view-height))] mt-[calc(var(--top-tool-height)+var(--tags-view-height))]':
fixedHeader.value
}
]}
>
<div
class={[
{
'fixed top-0 left-0 z-10': fixedHeader.value,
'w-[calc(100%-var(--left-menu-min-width))] !left-[var(--left-menu-min-width)]':
collapse.value && fixedHeader.value && !mobile.value,
'w-[calc(100%-var(--left-menu-max-width))] !left-[var(--left-menu-max-width)]':
!collapse.value && fixedHeader.value && !mobile.value,
'!w-full !left-0': mobile.value
}
]}
style="transition: all var(--transition-time-02);"
>
<ToolHeader
class={[
'bg-[var(--top-header-bg-color)]',
{
'layout-border__bottom': !tagsView.value
}
]}
></ToolHeader>
{tagsView.value ? (
<TagsView class="layout-border__top layout-border__bottom"></TagsView>
) : undefined}
</div>
<AppView></AppView>
</ElScrollbar>
</div>
</>
)
}
const route = useRoute()
let renderClassic = null;
if(route.path == "/kefu/kefu"){
renderClassic = () => {
return (
<>
<div
style="transition: all var(--transition-time-02);width:85%;margin:0 auto;"
>
<ElScrollbar
v-loading={pageLoading.value}
class={[
`${prefixCls}-content-scrollbar`,
{
}
]}
>
<AppView></AppView>
</ElScrollbar>
</div>
</>
)
}
}else {
renderClassic = () => {
return (
<>
<div
class={[
'absolute top-0 left-0 h-full layout-border__right',
{ '!fixed z-3000': mobile.value }
]}
>
{logo.value ? (
<Logo
class={[
'bg-[var(--left-menu-bg-color)] relative',
{
'!pl-0': mobile.value && collapse.value,
'w-[var(--left-menu-min-width)]': appStore.getCollapse,
'w-[var(--left-menu-max-width)]': !appStore.getCollapse
}
]}
style="transition: all var(--transition-time-02);"
></Logo>
) : undefined}
<Menu class={[{ '!h-[calc(100%-var(--logo-height))]': logo.value }]}></Menu>
</div>
<div
class={[
`${prefixCls}-content`,
'absolute top-0 h-[100%]',
{
'w-[calc(100%-var(--left-menu-min-width))] left-[var(--left-menu-min-width)]':
collapse.value && !mobile.value && !mobile.value,
'w-[calc(100%-var(--left-menu-max-width))] left-[var(--left-menu-max-width)]':
!collapse.value && !mobile.value && !mobile.value,
'fixed !w-full !left-0': mobile.value
}
]}
style="transition: all var(--transition-time-02);"
>
<ElScrollbar
v-loading={pageLoading.value}
class={[
`${prefixCls}-content-scrollbar`,
{
'!h-[calc(100%-var(--top-tool-height)-var(--tags-view-height))] mt-[calc(var(--top-tool-height)+var(--tags-view-height))]':
fixedHeader.value
}
]}
>
<div
class={[
{
'fixed top-0 left-0 z-10': fixedHeader.value,
'w-[calc(100%-var(--left-menu-min-width))] !left-[var(--left-menu-min-width)]':
collapse.value && fixedHeader.value && !mobile.value,
'w-[calc(100%-var(--left-menu-max-width))] !left-[var(--left-menu-max-width)]':
!collapse.value && fixedHeader.value && !mobile.value,
'!w-full !left-0': mobile.value
}
]}
style="transition: all var(--transition-time-02);"
>
<ToolHeader
class={[
'bg-[var(--top-header-bg-color)]',
{
'layout-border__bottom': !tagsView.value
}
]}
></ToolHeader>
{tagsView.value ? (
<TagsView class="layout-border__top layout-border__bottom"></TagsView>
) : undefined}
</div>
<AppView></AppView>
</ElScrollbar>
</div>
</>
)
}
}
const renderTopLeft = () => {
return (

View File

@ -126,6 +126,8 @@ export enum DICT_TYPE {
INFRA_FILE_TYPE = 'infra_file_type',
PAY_WALLET_RECHARGE_PAY_STATUS = 'pay_wallet_recharge_pay_status',
//预约:项目
SUBSCRIBE_PROJECT_STATUS = 'subscribe_project_status',

View File

@ -1,376 +1,420 @@
<template>
<doc-alert title="上传下载" url="https://doc.iocoder.cn/file/" />
<doc-alert title="上传下载" url="https://doc.iocoder.cn/file/" />
<div class="flex-container">
<!-- 菜单区域 -->
<div class="menu-area">
<div class="flex-container">
<!-- 菜单区域 -->
<div class="menu-area">
<el-button type="primary" plain @click="createType" style="width: 90;font-size: 12px;">
<Icon icon="ep:plus" class="mr-5px" /> 新增分类
</el-button>
<el-button type="primary" plain @click="createType" style="width: 90;font-size: 12px;">
<Icon icon="ep:plus" class="mr-5px" /> 新增分类
</el-button>
<el-menu :default-active="targetMenuId" style="width:182px">
<el-menu-item :index="targetMenuId" :key="targetMenuId" @click="clickMenu(targetMenuId)">
全部类型
</el-menu-item>
<el-menu-item v-for="item in typeMenu" :index="item.value" :key="item.value"
@click="clickMenu(item.value)">
{{ item.label }}
<el-icon style="margin-left: 60px;width: 10px;" @mouseover="showActions = item.value"
@mouseleave="showActions = null">
<MoreFilled />
<div v-if="showActions === item.value" class="action-buttons">
<el-button size="small" @click.stop="editItem(item.id,item.label)">编辑</el-button>
<br />
<el-button size="small" @click.stop="deleteItem(item.id)">删除</el-button>
</div>
</el-icon>
</el-menu-item>
</el-menu>
<el-menu :default-active="targetMenuId" style="width:182px">
<el-menu-item :index="targetMenuId" :key="targetMenuId" @click="clickMenu(targetMenuId)">
全部类型
</el-menu-item>
<el-menu-item v-for="item in typeMenu" :index="item.value" :key="item.value"
@click="clickMenu(item.value)">
{{ item.label }}
<el-icon style="margin-left: 60px;width: 10px;" @mouseover="showActions = item.value"
@mouseleave="showActions = null">
<MoreFilled />
<div v-if="showActions === item.value" class="action-buttons">
<el-button size="small" @click.stop="editItem(item.id,item.label)">编辑</el-button>
<br />
<el-button size="small" @click.stop="deleteItem(item.id)">删除</el-button>
</div>
</el-icon>
</el-menu-item>
</el-menu>
</div>
</div>
<!-- 内容区域 -->
<div class="content-wrap">
<ContentWrap>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
<!-- <el-form-item label="文件路径" prop="path">
<el-input
v-model="queryParams.path"
placeholder="请输入文件路径"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item> -->
<el-form-item label="文件类型" prop="type" width="80">
<el-input v-model="queryParams.type" placeholder="请输入文件类型" clearable
@keyup.enter="handleQuery" />
</el-form-item>
<!-- 内容区域 -->
<div class="content-wrap">
<ContentWrap>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
<el-form-item label="文件类型" prop="type" width="80">
<el-input v-model="queryParams.type" placeholder="请输入文件类型" clearable
@keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker 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')]" />
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> 搜索
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> 重置
</el-button>
<el-button type="primary" plain @click="openForm">
<Icon icon="ep:upload" class="mr-5px" /> 上传文件
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker 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')]" />
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> 搜索
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> 重置
</el-button>
<el-button type="primary" plain @click="openForm">
<Icon icon="ep:upload" class="mr-5px" /> 上传文件
</el-button>
</el-form-item>
<span><img @click="liebiao" style="cursor: pointer;" class="mr-10px h-30px w-30px" src="@/assets/imgs/liebiao.png" /></span>
<span><img @click="tubiao" style="cursor: pointer;" class="mr-10px h-30px w-30px" src="@/assets/imgs/tubiao.png" /></span>
</el-form>
</ContentWrap>
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="文件内容" align="center" prop="url" width="110px">
<template #default="{ row }">
<el-image v-if="row.type.includes('image')" class="h-80px w-80px" lazy :src="row.url"
:preview-src-list="[row.url]" preview-teleported fit="cover" />
<el-link v-else-if="row.type.includes('pdf')" type="primary" :href="row.url"
:underline="false" target="_blank">预览</el-link>
<el-link v-else type="primary" download :href="row.url" :underline="false"
target="_blank">下载</el-link>
</template>
</el-table-column>
<el-table-column label="文件名" align="center" prop="name" :show-overflow-tooltip="true" />
<!-- <el-table-column label="文件路径" align="center" prop="path" :show-overflow-tooltip="true" /> -->
<el-table-column label="URL" align="center" prop="url" :show-overflow-tooltip="true" />
<!-- <el-table-column label="文件大小" align="center" prop="size" width="120"
:formatter="fileSizeFormatter" /> -->
<!-- <el-table-column label="文件类型" align="center" prop="type" width="180px" />
<el-table-column label="图片分类" align="center" prop="picType">
<template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_FILE_TYPE" :value="scope.row.picType" />
</template>
</el-table-column> -->
<el-table-column label="上传时间" align="center" prop="createTime" width="180"
:formatter="dateFormatter" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button link type="primary"
@click="updateForm('update', scope.row.id , scope.row.picType)">
更改类型
</el-button>
<el-button link type="danger" @click="handleDelete(scope.row.id)"
v-hasPermi="['infra:file:delete']">
删除
</el-button>
</template>
</el-table-column>
</el-table>
</ContentWrap>
<!-- 分页 -->
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList" />
<el-table v-loading="loading" :data="list" v-show="panduan == '1'">
<el-table-column label="文件内容" align="center" prop="url" width="110px">
<template #default="{ row }">
<el-image v-if="row.type.includes('image')" class="h-80px w-80px" lazy :src="row.url"
:preview-src-list="[row.url]" preview-teleported fit="cover" />
<el-link v-else-if="row.type.includes('pdf')" type="primary" :href="row.url"
:underline="false" target="_blank">预览</el-link>
<el-link v-else type="primary" download :href="row.url" :underline="false"
target="_blank">下载</el-link>
</template>
</el-table-column>
<el-table-column label="文件名" align="center" prop="name" :show-overflow-tooltip="true" />
<!-- <el-table-column label="文件路径" align="center" prop="path" :show-overflow-tooltip="true" /> -->
<el-table-column label="URL" align="center" prop="url" :show-overflow-tooltip="true" />
</div>
</div>
<!-- 表单弹窗添加/修改 -->
<FileForm ref="formRef" @success="getList" />
<el-table-column label="上传时间" align="center" prop="createTime" width="180"
:formatter="dateFormatter" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button link type="primary"
@click="updateForm('update', scope.row.id , scope.row.picType)">
更改类型
</el-button>
<el-button link type="danger" @click="handleDelete(scope.row.id)"
v-hasPermi="['infra:file:delete']">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<UpdateForm ref="forRef" @success="getList" />
<div v-show="panduan == '2'">
<!-- 图片展示区域 -->
<div class="image-container">
<div v-for="item in tubiaoData" :key="item.id" class="image-item">
<img :src="item.url" alt="图表" />
</div>
</div>
</div>
<Dialog v-model="dialogVisibles" :title="dialogTitles">
<el-form ref="formRef" v-loading="formLoading" :model="formData" :rules="formRules" label-width="80px">
<el-form-item label="数据标签" prop="label">
<el-input v-model="formData.label" placeholder="请输入数据标签" />
</el-form-item>
<el-form-item label="显示排序" prop="sort">
<el-input-number v-model="formData.sort" :min="0" controls-position="right" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisibles = false"> </el-button>
</template>
</Dialog>
<!-- 分页 -->
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList" />
<Dialog v-model="dialogVisible" :title="dialogTitle">
<el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="80px">
<el-form-item label="数据标签" prop="label">
<el-input v-model="updateLabel" placeholder="请输入数据标签" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForms"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</div>
</div>
<!-- 表单弹窗添加/修改 -->
<FileForm ref="formRef" @success="getList" />
<UpdateForm ref="forRef" @success="getList" />
<Dialog v-model="dialogVisibles" :title="dialogTitles">
<el-form ref="formRef" v-loading="formLoading" :model="formData" :rules="formRules" label-width="80px">
<el-form-item label="数据标签" prop="label">
<el-input v-model="formData.label" placeholder="请输入数据标签" />
</el-form-item>
<el-form-item label="显示排序" prop="sort">
<el-input-number v-model="formData.sort" :min="0" controls-position="right" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="dialogVisibles = false"> </el-button>
</template>
</Dialog>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="80px">
<el-form-item label="数据标签" prop="label">
<el-input v-model="updateLabel" placeholder="请输入数据标签" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForms"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { fileSizeFormatter } from '@/utils'
import { Search, MoreFilled } from '@element-plus/icons-vue';
import { dateFormatter } from '@/utils/formatTime'
// import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import * as FileApi from '@/api/infra/file'
import FileForm from './FileForm.vue'
import UpdateForm from './updateForm.vue'
import * as DictDataApi from '@/api/system/dict/dict.data'
const typeMenu = ref<DictDataApi.DictDataVO[]>([]) //
const targetMenuId = ref('0')
defineOptions({ name: 'InfraFile' })
const showActions = ref(null);
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const dialogVisibles = ref(false) //
const dialogTitles = ref('') //
const formLoading = ref(false) // 12
const formData = ref({
id: undefined,
sort: undefined,
label: '',
value: '',
dictType: '',
// status: CommonStatusEnum.ENABLE,
colorType: '',
cssClass: '',
remark: ''
})
const formRules = reactive({
label: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
sort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }],
})
const updateLabel = ref('')
const message = useMessage() //
const { t } = useI18n() //
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { fileSizeFormatter } from '@/utils'
import { Search, MoreFilled } from '@element-plus/icons-vue';
import { dateFormatter } from '@/utils/formatTime'
// import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import * as FileApi from '@/api/infra/file'
import FileForm from './FileForm.vue'
import UpdateForm from './updateForm.vue'
import * as DictDataApi from '@/api/system/dict/dict.data'
const typeMenu = ref<DictDataApi.DictDataVO[]>([]) //
const targetMenuId = ref('0')
defineOptions({ name: 'InfraFile' })
const showActions = ref(null);
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const dialogVisibles = ref(false) //
const dialogTitles = ref('') //
const formLoading = ref(false) // 12
const panduan = ref('1')
const tubiaoData = ref<FileDataVO[]>([]);
const formData = ref({
id: undefined,
sort: undefined,
label: '',
value: '',
dictType: '',
// status: CommonStatusEnum.ENABLE,
colorType: '',
cssClass: '',
remark: ''
})
const formRules = reactive({
label: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
sort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }],
})
const updateLabel = ref('')
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const menuId = ref()
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const menuId = ref()
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
type: undefined,
picType: '',
path: undefined,
createTime: [],
})
const queryFormRef = ref() //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
type: undefined,
picType: '',
path: undefined,
createTime: [],
})
const queryFormRef = ref() //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await FileApi.getFilePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await FileApi.getFilePage(queryParams)
list.value = data.list
tubiaoData.value = data.list
console.log('111111',tubiaoData)
total.value = data.total
} finally {
loading.value = false
}
}
interface FileDataVO {
id: string
configId: string
path: string
name: string
url: string
type: string
picType: string
size: string
createTime: Date
}
//
const createType = () => {
dialogVisibles.value = true
dialogTitles.value = '新增分类'
const tubiao = () => {
panduan.value = '2'
}
}
const liebiao = () => {
panduan.value = '1'
}
/** 添加分类菜单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as DictDataApi.DictDataVO
await DictDataApi.createPicType(data)
message.success(t('common.createSuccess'))
dialogVisibles.value = false
//
emit('success')
getTypeList()
//
const createType = () => {
dialogVisibles.value = true
dialogTitles.value = '新增分类'
}
} finally {
formLoading.value = false
}
}
/** 添加分类菜单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
const data = formData.value as DictDataApi.DictDataVO
await DictDataApi.createPicType(data)
message.success(t('common.createSuccess'))
dialogVisibles.value = false
//
emit('success')
getTypeList()
//
const submitForms = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
} finally {
formLoading.value = false
}
}
await DictDataApi.updateMenu(menuId.value, updateLabel.value)
message.success(t('common.createSuccess'))
dialogVisible.value = false
//
emit('success')
getTypeList()
//
const submitForms = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
} finally {
formLoading.value = false
}
}
await DictDataApi.updateMenu(menuId.value, updateLabel.value)
message.success(t('common.createSuccess'))
dialogVisible.value = false
//
emit('success')
getTypeList()
//
const editItem = (id : number | undefined, lable : string) => {
dialogVisible.value = true
dialogTitle.value = '菜单编辑'
menuId.value = id
updateLabel.value = lable
//
console.log('编辑:',);
};
//
const deleteItem = async (id : number | undefined) => {
//
//
await message.delConfirm()
await DictDataApi.deleteMenu(id)
message.success(t('common.delSuccess'))
getTypeList()
} finally {
formLoading.value = false
}
}
};
//
const getTypeList = async () => {
const data = await DictDataApi.getTypeList()
typeMenu.value = data
console.log('1111111111', typeMenu)
}
//
const editItem = (id : number | undefined, lable : string) => {
dialogVisible.value = true
dialogTitle.value = '菜单编辑'
menuId.value = id
updateLabel.value = lable
//
console.log('编辑:',);
};
//
const deleteItem = async (id : number | undefined) => {
//
//
await message.delConfirm()
await DictDataApi.deleteMenu(id)
message.success(t('common.delSuccess'))
getTypeList()
};
//
const getTypeList = async () => {
const data = await DictDataApi.getTypeList()
typeMenu.value = data
}
/** */
const clickMenu = (id : string) => {
queryParams.picType = id
queryParams.pageNo = 1
getList()
}
/** */
const clickMenu = (id : string) => {
queryParams.picType = id
queryParams.pageNo = 1
getList()
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
const formRef = ref()
const openForm = () => {
formRef.value.open()
}
const formRef = ref()
const openForm = () => {
formRef.value.open()
}
const forRef = ref()
/** 修改操作 */
const updateForm = (type : string, id : number, picType : number) => {
forRef.value.open(type, id, picType)
}
const forRef = ref()
/** 修改操作 */
const updateForm = (type : string, id : number, picType : number) => {
forRef.value.open(type, id, picType)
}
/** 删除按钮操作 */
const handleDelete = async (id : number) => {
try {
//
await message.delConfirm()
//
await FileApi.deleteFile(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch { }
}
/** 删除按钮操作 */
const handleDelete = async (id : number) => {
try {
//
await message.delConfirm()
//
await FileApi.deleteFile(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch { }
}
/** 初始化 **/
onMounted(() => {
queryParams.picType = targetMenuId.value
getTypeList()
getList()
})
/** 初始化 **/
onMounted(() => {
queryParams.picType = targetMenuId.value
getTypeList()
getList()
})
</script>
<style scoped>
.flex-container {
height: 100vh;
/* 使容器填满视口高度 */
display: flex;
}
.flex-container {
height: 100vh;
/* 使容器填满视口高度 */
display: flex;
}
.menu-area {
padding: 25px;
background-color: white;
width: 180px;
height: 1000px;
/* 固定高度 */
overflow-y: hidden;
/* 禁止滚动 */
border-right: 1px solid #e0e0e0;
/* 可选:添加分隔线 */
}
.menu-area {
padding: 25px;
background-color: white;
width: 180px;
height: 1000px;
/* 固定高度 */
overflow-y: hidden;
/* 禁止滚动 */
border-right: 1px solid #e0e0e0;
/* 可选:添加分隔线 */
}
.content-wrap {
flex-grow: 1;
/* 使内容区域占据剩余空间 */
overflow-y: auto;
/* 允许内容区域滚动 */
}
</style>
.content-wrap {
flex-grow: 1;
/* 使内容区域占据剩余空间 */
overflow-y: auto;
/* 允许内容区域滚动 */
}
/* 图片容器样式 */
.image-container {
display: flex;
flex-wrap: wrap;
gap: 10px; /* 设置图片之间的间距 */
justify-content: flex-start; /* 如果图片少于5张会平均分布 */
max-width: 1000px; /* 设置外部容器的最大宽度 */
margin: 0 auto; /* 居中显示 */
}
/* 每张图片占用的宽度使每行显示5张 */
.image-item {
width: calc(20% - 8px); /* 宽度设为容器的20%,减去间距 */
}
/* 设置图片的最大宽度 */
.image-item img {
width: 100%;
height: auto;
border-radius: 8px; /* 图片边缘圆角(可选) */
}
</style>

View File

@ -67,9 +67,9 @@
@click="openForm('create')"
v-hasPermi="['promotion:auto-response:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
<Icon icon="ep:plus" class="mr-5px" /> 新增自动恢复
</el-button>
<el-button
<!-- <el-button
type="success"
plain
@click="handleExport"
@ -77,7 +77,7 @@
v-hasPermi="['promotion:auto-response:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-button> -->
</el-form-item>
</el-form>
</ContentWrap>

View File

@ -0,0 +1,124 @@
<template>
<div class="main">
<div class="mainTop">
<div>商品分类</div>
<div class="right">
<div class="save" @click="save">保存</div>
<div class="cz" @click="cz">重置</div>
</div>
</div>
<div class="mainBottom">
<div class='item' :class="currItem == 1 ? 'on': ''" @click="clickItem(1)">
<img class="img" src="https://zysc.fjptzykj.com:3000/shangcheng/a7d0409cbf5335a2780409756914c530fc7c88bec85fc81302b53760d9be4a03.jpg" />
<div class="text">样式1</div>
</div>
<div class='item' :class="currItem == 2 ? 'on': ''" @click="clickItem(2)">
<img class="img" src="https://zysc.fjptzykj.com:3000/shangcheng/9096b2c04a6e46ea562999a93a1b975100c6b4557e680dcb31de6d3555407841.png" />
<div class="text">样式2</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
// TODO @ decorate index.vue
import * as DiyTemplateApi from '@/api/mall/promotion/diy/template'
import { useTagsdivStore } from '@/store/modules/tagsdiv'
import { DiyComponentLibrary, PAGE_LIBS } from '@/components/DiyEditor/util' // DIY DiyEditor
import { toNumber } from 'lodash-es'
const message = useMessage() //
const currItem = ref();
function clickItem (val){
currItem.value = val;
}
function cz (val){
currItem.value = 1;
}
function save (){
setProjuctClass(currItem.value);
// console.log("")
}
//
const setProjuctClass = async (id) => {
const res = await DiyTemplateApi.setDiyProjuctClass(id);
console.log(res, "sssss");
if(res){
message.success('保存成功')
}
}
//
const getProjuctClass = async () => {
const res = await DiyTemplateApi.getDiyProjuctClass();
currItem.value = res
}
getProjuctClass()
</script>
<style lang="scss" scoped>
.main{
display:flex;
flex-wrap:wrap;
.mainTop{
width:100%;
margin:10px 10px;
background:white;
padding:10px 20px;
font-weight:700;
display:flex;
justify-content:space-between;
align-items: center;
.right{
display:flex;
div{
padding:5px 15px;
font-weight:400;
font-size: 14px;
}
.save{
background:#0256FF;
margin-right:10px;
color:white;
cursor: pointer;
}
.cz{
border:1px solid #cccccc;
cursor: pointer;
}
}
}
.mainBottom{
width:100%;
margin:10px 10px;
background:white;
padding:30px 30px;
display:flex;
.item{
margin-right:20px;
text-align: center;
border-radius: 12px;
cursor: pointer;
&.on{
.text{
color:#0256ff;
}
.img{
border:2px solid #0256ff;
}
}
.img{
width:260px;
border-radius: 12px;
border:2px solid white;
}
.text{
margin-top:10px;
}
}
}
}
</style>

View File

@ -0,0 +1,195 @@
<template>
<div class="main">
<div class="mainTop">
<div>主题风格</div>
<div class="right">
<div class="save" @click="save">保存</div>
<!-- <div class="cz" @click="cz">重置</div> -->
</div>
</div>
<div class="ztfg">
<div class='top'>
<div class="item" :class="currItem== '' ? 'on': ''" @click="clickItem('')">
<div class="le" style="background: rgb(233, 52, 34);"></div>
<div class="ri">红色</div>
</div>
<div class="item" :class="currItem== 'blue' ? 'on': ''" @click="clickItem('blue')">
<div class="le"></div>
<div class="ri">天空蓝</div>
</div>
<div class="item" :class="currItem== 'lv' ? 'on': ''" @click="clickItem('lv')">
<div class="le" style="background:rgb(66, 202, 77);"></div>
<div class="ri">生鲜绿</div>
</div>
</div>
<div class="mainBottom">
<div class='item' v-show="currItem== ''">
<img class="img"
src="https://zysc.fjptzykj.com:3000/shangcheng/667bbed3e719df73217b10d743eb880c1719cb294b6f50a81f78f597fdbcf351.png" style="width:300px;" />
</div>
<div class='item' v-show="currItem== 'blue'">
<img class="img"
src="https://zysc.fjptzykj.com:3000/shangcheng/4bffe9f0cee9605262a579ee45156c9e37a16e2a24035a0e49b8a4433075f793.jpg" />
</div>
<div class='item' v-show="currItem== 'lv'">
<img class="img"
src="https://zysc.fjptzykj.com:3000/shangcheng/0d0a7ab210afb5cee674e402ca3ec197a30523687acbec2e8e5f16fb52075e9b.jpg" />
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
// TODO @ decorate index.vue
import * as DiyTemplateApi from '@/api/mall/promotion/diy/template'
import * as DiyPageApi from '@/api/mall/promotion/diy/page'
import { useTagsdivStore } from '@/store/modules/tagsdiv'
import { DiyComponentLibrary, PAGE_LIBS } from '@/components/DiyEditor/util' // DIY DiyEditor
import { toNumber } from 'lodash-es'
const message = useMessage() //
const currItem = ref('blue');
function clickItem(val) {
currItem.value = val;
}
function cz(val) {
currItem.value = 'blue';
}
function save() {
const val = '';
// currItem.value = val;
if(currItem.value == 'lv'){
setZtClass('lv')
}else if(currItem.value == 'blue'){
setZtClass('blue')
}else{
setZtClass('')
}
// console.log("")
}
//
const setZtClass = async (id) => {
const res = await DiyTemplateApi.setDiyZtClass(id);
console.log(res, "sssss");
if(res || res == ''){
message.success('保存成功')
}
}
//
const getZtClass = async () => {
const res = await DiyTemplateApi.getDiyZtClass();
currItem.value = res
}
getZtClass()
</script>
<style lang="scss" scoped>
.main {
display: flex;
flex-wrap: wrap;
.mainTop {
width: 100%;
margin: 10px 10px;
background: white;
padding: 10px 20px;
font-weight: 700;
display: flex;
justify-content: space-between;
align-items: center;
.right {
display: flex;
div {
padding: 5px 15px;
font-weight: 400;
font-size: 14px;
}
.save {
background: #0256FF;
margin-right: 10px;
color: white;
cursor: pointer;
}
.cz {
border: 1px solid #cccccc;
cursor: pointer;
}
}
}
.ztfg {
width: 100%;
margin: 10px 10px;
background: white;
padding: 30px 30px;
.top{
display: flex;
margin-bottom: 10px;
.item{
padding:10px 15px;
border:1px solid #cccccc;
border-radius:6px;
margin-right:10px;
display:flex;
align-items:center;
cursor: pointer;
&.on{
border:1px solid #0256ff;
}
.le{
margin-right:10px;
border-radius:6px;
width:25px;
height:25px;
background:rgb(28, 165, 233);
}
.ti{
}
}
}
.mainBottom {
display: flex;
.item {
margin-right: 20px;
text-align: center;
border-radius: 12px;
// &.on {
// .text {
// color: #0256ff;
// }
// .img {
// border: 2px solid #0256ff;
// }
// }
.img {
width: 800px;
border-radius: 12px;
border: 2px solid white;
}
.text {
margin-top: 10px;
}
}
}
}
}
</style>

View File

@ -1,5 +1,6 @@
<template>
<div class="kefu">
<div
v-for="item in conversationList"
:key="item.id"
@ -9,7 +10,7 @@
@contextmenu.prevent="rightClick($event as PointerEvent, item)"
>
<div class="flex justify-center items-center w-100%">
<div class="flex justify-center items-center w-50px h-50px">
<div class="flex justify-center items-center w-40px h-40px">
<!-- 头像 + 未读 -->
<el-badge
:hidden="item.adminUnreadMessageCount === 0"
@ -19,10 +20,10 @@
<el-avatar :src="item.userAvatar" alt="avatar" />
</el-badge>
</div>
<div class="ml-10px w-100%">
<div class="flex justify-between items-center w-100%">
<div class="ml-3px w-full">
<div class="flex justify-between items-center w-full">
<span class="username">{{ item.userNickname }}</span>
<span class="color-[var(--left-menu-text-color)]" style="font-size: 13px">
<span class="color-[var(--left-menu-text-color)]" style="font-size: 12px">
{{ formatPast(item.lastMessageTime, 'YYYY-MM-DD') }}
</span>
</div>
@ -182,12 +183,13 @@ watch(showRightMenu, (val) => {
<style lang="scss" scoped>
.kefu {
&-conversation {
height: 60px;
padding: 10px;
height: 55px;
padding: 8px;
//background-color: #fff;
transition: border-left 0.05s ease-in-out; /* 设置过渡效果 */
.username {
font-size: 20%;
min-width: 0;
max-width: 60%;
overflow: hidden;
@ -198,7 +200,7 @@ watch(showRightMenu, (val) => {
}
.last-message {
font-size: 13px;
font-size: 15px;
width: 200px;
overflow: hidden; //
white-space: nowrap; //

View File

@ -1,17 +1,18 @@
<template>
<el-container v-if="showKeFuMessageList" class="kefu">
<el-header>
<!-- <el-header>
<div class="kefu-title">{{ conversation.userNickname }}</div>
</el-header>
</el-header> -->
<el-main class="kefu-content overflow-visible">
<el-scrollbar ref="scrollbarRef" always height="calc(100vh - 495px)" @scroll="handleScroll">
<el-scrollbar ref="scrollbarRef" always height="calc(100vh - 390px)" @scroll="handleScroll">
<div v-if="refreshContent" ref="innerRef" class="w-[100%] pb-3px">
<!-- 消息列表 -->
<div v-for="(item, index) in getMessageList0" :key="item.id" class="w-[100%]">
<div class="flex justify-center items-center mb-20px">
<!-- 日期 -->
<div
v-if="
<div v-if="
item.contentType !== KeFuMessageContentTypeEnum.SYSTEM && showTime(item, index)
" class="date-message">
{{ formatDate(item.createTime) }}
@ -21,19 +22,18 @@ v-if="
{{ item.content }}
</div>
</div>
<div
:class="[
<div :class="[
item.senderType === UserTypeEnum.MEMBER
? `ss-row-left`
: item.senderType === UserTypeEnum.ADMIN
? `ss-row-right`
: ''
]" class="flex mb-20px w-[100%]">
<el-avatar
v-if="item.senderType === UserTypeEnum.MEMBER" :src="conversation.userAvatar"
<el-avatar v-if="item.senderType === UserTypeEnum.MEMBER" :src="conversation.userAvatar"
alt="avatar" class="w-60px h-60px" />
<div
:class="{ 'kefu-message': KeFuMessageContentTypeEnum.TEXT === item.contentType }"
<div :class="{ 'kefu-message': KeFuMessageContentTypeEnum.TEXT === item.contentType }"
class="p-10px">
<!-- 文本消息 -->
<MessageItem :message="item">
@ -44,15 +44,13 @@ v-if="item.senderType === UserTypeEnum.MEMBER" :src="conversation.userAvatar"
</MessageItem>
<!-- 图片消息 -->
<MessageItem :message="item">
<el-image
v-if="KeFuMessageContentTypeEnum.IMAGE === item.contentType"
<el-image v-if="KeFuMessageContentTypeEnum.IMAGE === item.contentType"
:initial-index="0" :preview-src-list="[item.content]" :src="item.content"
class="w-200px" fit="contain" preview-teleported />
</MessageItem>
<!-- 商品消息 -->
<MessageItem :message="item">
<ProductItem
v-if="KeFuMessageContentTypeEnum.PRODUCT === item.contentType"
<ProductItem v-if="KeFuMessageContentTypeEnum.PRODUCT === item.contentType"
:spuId="getMessageContent(item).spuId" :picUrl="getMessageContent(item).picUrl"
:price="getMessageContent(item).price"
:skuText="getMessageContent(item).introduction"
@ -61,50 +59,53 @@ v-if="KeFuMessageContentTypeEnum.PRODUCT === item.contentType"
</MessageItem>
<!-- 订单消息 -->
<MessageItem :message="item">
<OrderItem
v-if="KeFuMessageContentTypeEnum.ORDER === item.contentType"
<OrderItem v-if="KeFuMessageContentTypeEnum.ORDER === item.contentType"
:message="item" class="max-w-100%" />
</MessageItem>
</div>
<el-avatar
v-if="item.senderType === UserTypeEnum.ADMIN" :src="item.senderAvatar"
<el-avatar v-if="item.senderType === UserTypeEnum.ADMIN" :src="item.senderAvatar"
alt="avatar" />
</div>
</div>
</div>
</el-scrollbar>
<div
v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer"
<div v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer"
@click="handleToNewMessage">
<span>有新消息</span>
<Icon class="ml-5px" icon="ep:bottom" />
</div>
</el-main>
<el-footer height="230px">
<el-divider style="margin: 4px 0;" />
<el-footer height="185px">
<div class="h-[100%]">
<div class="chat-tools flex items-center">
<div class="flex items-center">
<EmojiSelectPopover @select-emoji="handleEmojiSelect" />
<PictureSelectUpload class="ml-15px mt-3px cursor-pointer" @send-picture="handleSendPicture" />
<!-- <VerbalTrick class="ml-11px mt-5px cursor-pointer" /> -->
<!-- 话术库 -->
<div style="margin-left: 9px; margin-top:5px;cursor: pointer;">
<img :src="Picture" class="w-32px h-32px" @click="huashu" />
<div style="margin-left: 15px; margin-top:4px;cursor: pointer;">
<img :src="xiaoxi" class="w-22px h-22px" @click="huashu" />
</div>
<!-- 转接按钮 -->
<el-dropdown placement="top" style="margin-left: auto;margin-right: 15px; margin-top:5px;margin-top:5px;cursor: pointer;" ref="dropdown1" trigger="contextmenu">
<el-dropdown placement="top"
style="margin-left: auto;margin-right: 15px;cursor: pointer;"
ref="dropdown1" trigger="contextmenu">
<div>
<img :src="Picture2" class="w-27px h-27px" @click="getOnlineStaffList" title="转接"/>
<img :src="Picture2" class="w-62px h-20px" @click="getOnlineStaffList" title="转接" />
</div>
<template #dropdown>
<el-dropdown-item v-for="staff in onlineStaffList" :key="staff.id" :disabled="staff.id===getStaffToken()" @click="transferConversion(staff.id)">
<el-dropdown-item v-for="staff in onlineStaffList" :key="staff.id"
:disabled="staff.id===getStaffToken()" @click="transferConversion(staff.id)">
{{ staff.name }}
</el-dropdown-item>
</template>
</el-dropdown>
</div>
<el-input v-model="message" :rows="6" style="border-style: none" type="textarea" />
<br/>
<textarea style="border: none; outline: none;" v-model="message" placeholder="请输入文字内容" rows="5" cols="75"></textarea>
<div class="h-45px flex justify-end">
<el-button class="mt-10px" type="primary" @click="handleSendMessage">发送</el-button>
</div>
@ -120,12 +121,15 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
<!-- 左边占 30% -->
<div style="flex: 0 0 20%; padding: 10px;">
<el-menu :default-active="targetMenuId">
<el-menu-item v-for="item in huashuType" :index="item.value" :key="item.value" @click="clickMenu(item.value)">{{item.label}}</el-menu-item>
<el-menu-item v-for="item in huashuType" :index="item.value" :key="item.value"
@click="clickMenu(item.value)">{{item.label}}</el-menu-item>
</el-menu>
</div>
<!-- 右边占 70% -->
<div style="flex: 1; padding: 5px; overflow-y: auto; max-height: 400px;">
<p v-for="item in verbalTrickList" :key="item.id" style="font-size: 12px;cursor: pointer;transition: background-color 0.3s;" class="hover-shadow" @click="huashuClick(item.details)">
<p v-for="item in verbalTrickList" :key="item.id"
style="font-size: 12px;cursor: pointer;transition: background-color 0.3s;" class="hover-shadow"
@click="huashuClick(item.details)">
{{item.details}}
</p>
</div>
@ -143,7 +147,8 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
import PictureSelectUpload from './tools/PictureSelectUpload.vue'
// import VerbalTrick from './tools/VerbalTrick.vue'
import Picture from '@/views/mall/promotion/kefu/components/asserts/huashu.png'
import Picture2 from '@/views/mall/promotion/kefu/components/asserts/zhuanjie.png'
import xiaoxi from '@/views/mall/promotion/kefu/components/asserts/xiaoxi.png'
import Picture2 from '@/views/mall/promotion/kefu/components/asserts/zj.png'
import ProductItem from './message/ProductItem.vue'
import OrderItem from './message/OrderItem.vue'
import { Emoji, useEmoji } from './tools/emoji'
@ -155,11 +160,11 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
import relativeTime from 'dayjs/plugin/relativeTime'
import { debounce } from 'lodash-es'
import { jsonParse } from '@/utils'
import { getStaffToken, setStaffToken} from '@/utils/auth'
import { getStaffToken, setStaffToken } from '@/utils/auth'
import type { DropdownInstance } from 'element-plus'
import { ref } from 'vue'
import * as DictDataApi from '@/api/system/dict/dict.data'
const huashuType = ref<DictDataApi.DictDataVO[]>([]) //
const targetMenuId = ref('0')
@ -170,7 +175,7 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
let dialogVisible = ref(false) //
const dialogTitle = ref('客服话术') //
//
const verbalTrickList = ref<VerbalTrickVO[]>([])
const verbalTrickList = ref<VerbalTrickVO[]>([])
const message = ref('') //
const { replaceEmoji } = useEmoji()
const messageTool = useMessage()
@ -178,10 +183,12 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
const conversation = ref<KeFuConversationRespVO>({} as KeFuConversationRespVO) //
const showNewMessageTip = ref(false) //
import { SupportStaffApi, SupportStaffVO } from '@/api/mall/promotion/supportstaff' //
import { KeFuConversationApi} from '@/api/mall/promotion/kefu/conversation'
import { KeFuConversationApi } from '@/api/mall/promotion/kefu/conversation'
import { number } from 'vue-types'
const onlineStaffList = ref<SupportStaffVO[]>([]) // 线
const messages = useMessage() //
const kefuName = ref('')
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
@ -253,6 +260,7 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
/** 获得新会话的消息列表 */
// TODO @puhui999 list merge
const getNewMessageList = async (val : KeFuConversationRespVO) => {
// console.log('22222222',val)
// ,
queryParams.pageNo = 1
messageList.value = []
@ -297,25 +305,24 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
//
const getHuaShuTypeList = async () => {
const data = await DictDataApi.getHuaShuTypeList()
huashuType.value = data
huashuType.value = data
}
//
const getVerbalTrickList = async (id: string) => {
const getVerbalTrickList = async (id : string) => {
const response = await VerbalTrickApi.getVerbalTrickList(id);
verbalTrickList.value = response; // verbalTrickList
}
verbalTrickList.value = response; // verbalTrickList
}
/*选择话术库内容*/
const huashuClick = (content: string) => {
const huashuClick = (content : string) => {
message.value = content;
dialogVisible.value = false;
}
const clickMenu = (id: string) => {
console.log('1111111111',id)
const clickMenu = (id : string) => {
getVerbalTrickList(id)
}
/** 发送文本消息 */
const handleSendMessage = async () => {
@ -420,14 +427,26 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
status: 1,
})
onlineStaffList.value = data.list
} finally {
}
}
/** 转接客服人员列表 id会话id kefuId客服人员id */
const transferConversion = async (kefuId: number) => {
const transferConversion = async (kefuId : number) => {
try {
await KeFuConversationApi.transferConversion(queryParams.conversationId, kefuId)
const re = await KeFuConversationApi.transferConversion(queryParams.conversationId, kefuId)
kefuName.value = re
// 2.
const msg = {
conversationId: conversation.value.id,
contentType: KeFuMessageContentTypeEnum.SYSTEM,
content: '已为您转接至' + re,
}
await sendMessage(msg)
messages.success('转接成功')
conversation.value = null
} finally {
// todo
}
@ -435,15 +454,20 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
/** 初始化 **/
onMounted(() => {
getHuaShuTypeList()
getVerbalTrickList(targetMenuId.value)
getVerbalTrickList(targetMenuId.value)
})
</script>
<style lang="scss" scoped>
.hover-shadow:hover {
background-color: lightgray; /* 可根据需要调整颜色 */
background-color: lightgray;
/* 可根据需要调整颜色 */
}
.kefu {
&-title {
border-bottom: #e4e0e0 solid 1px;
@ -472,6 +496,7 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
.kefu-message {
margin-left: 20px;
position: relative;
&::before {
content: '';
@ -487,8 +512,8 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
}
}
}
.ss-row-right {
justify-content: flex-end;
@ -514,7 +539,7 @@ v-show="showNewMessageTip" class="newMessageTip flex items-center cursor-pointer
//
.kefu-message {
color: #a9a9a9;
color: #101010;
border-radius: 5px;
box-shadow: 3px 3px 5px rgba(220, 220, 220, 0.1);
padding: 5px 10px;

View File

@ -0,0 +1,62 @@
<template>
<div style="height: 522px ;" ref="userInfoRef" >
<div>
<span style="display: flex;">
<el-avatar
style="border: 1px solid #f8f9ee;"
src="https://zysc.fjptzykj.com:3000/shangcheng/29e07b7973621a1a9679744e18f2b4efd1fd50d857fe594879e813d67be3e2db.png"
/>
<span style="margin-left:5px;margin-top: 9px;">Marvin</span>
</span>
</div>
<el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;" />
<div>
<span style="color: #5d5d59;font-size: 13px ;">手机号</span><span style="margin-left: 47px;font-size: 14px ;">暂无</span>
</div>
<div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">分组</span><span style="margin-left: 60px;font-size: 14px ;">D类客户</span>
</div>
<div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">用户标签</span><span style="margin-left: 33px;font-size: 14px ;">小客户</span>
</div>
<el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;" />
<div>
<span style="color: #5d5d59;font-size: 13px ;">用户等级</span><span style="margin-left: 35px;font-size: 14px ;">暂无</span>
</div>
<div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">推荐人</span><span style="margin-left: 47px;font-size: 14px ;">客户</span>
</div>
<div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">用户类型</span><span style="margin-left: 33px;font-size: 14px ;">小客户</span>
</div>
<div>
<span style="color: #5d5d59;font-size: 13px ;">余额</span><span style="margin-left: 60px;font-size: 14px ;">暂无</span>
</div>
<div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">推广员</span><span style="margin-left: 47px;font-size: 14px ;">客户</span>
</div>
<div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">生日</span><span style="margin-left: 60px;font-size: 14px ;">小客户</span>
</div>
<el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;" />
</div>
</template>
<script >
import * as UserApi from '@/api/member/user'
// const user = ref<UserApi.UserVO>({} as UserApi.UserVO)
// const getUserId = async (id: string) => {
// user.value = await UserApi.getUserInfo(id)
// }
// defineExpose({ getUserId })
</script>
<style>
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -1,7 +1,7 @@
<!-- 目录是不是叫 member 好点然后这个组件是 MemberInfo里面有浏览足迹 -->
<template>
<div v-show="!isEmpty(conversation)" class="kefu">
<div class="header-title h-60px flex justify-center items-center">他的足迹</div>
<!-- <div class="header-title h-60px flex justify-center items-center">他的足迹</div> -->
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane label="最近浏览" name="a" />
<el-tab-pane label="订单列表" name="b" />

View File

@ -1,5 +1,6 @@
import KeFuConversationList from './KeFuConversationList.vue'
import KeFuMessageList from './KeFuMessageList.vue'
import MemberBrowsingHistory from './history/MemberBrowsingHistory.vue'
import UserInfo from './UserInfo.vue'
export { KeFuConversationList, KeFuMessageList, MemberBrowsingHistory }
export { KeFuConversationList, KeFuMessageList, MemberBrowsingHistory ,UserInfo}

View File

@ -2,7 +2,8 @@
<template>
<el-popover :width="500" placement="top" trigger="click">
<template #reference>
<Icon :size="30" class="ml-10px cursor-pointer" icon="twemoji:grinning-face" />
<!-- <Icon :size="30" class="ml-10px cursor-pointer" icon="twemoji:grinning-face" /> -->
<img :src="biaoqing" class="w-23px h-25px" />
</template>
<ElScrollbar height="300px">
<ul class="ml-2 flex flex-wrap px-2">
@ -26,8 +27,10 @@
<script lang="ts" setup>
defineOptions({ name: 'EmojiSelectPopover' })
import biaoqing from '@/views/mall/promotion/kefu/components/asserts/biaoqing.png'
import { Emoji, useEmoji } from './emoji'
const { getEmojiList } = useEmoji()
const emojiList = computed(() => getEmojiList())

View File

@ -1,12 +1,12 @@
<!-- 图片选择 -->
<template>
<div>
<img :src="Picture" class="w-35px h-35px" @click="selectAndUpload" />
<img :src="tupian" class="w-23px h-23px" @click="selectAndUpload" />
</div>
</template>
<script lang="ts" setup>
import Picture from '@/views/mall/promotion/kefu/components/asserts/picture.svg'
import tupian from '@/views/mall/promotion/kefu/components/asserts/tupian.png'
import * as FileApi from '@/api/infra/file'
defineOptions({ name: 'PictureSelectUpload' })

View File

@ -1,53 +1,206 @@
<template>
<el-row :gutter="10" style="display: flex; justify-content: center;">
<!-- 会话列表 -->
<el-col :span="5" >
<ContentWrap>
<KeFuConversationList ref="keFuConversationRef" @change="handleChange" />
</ContentWrap>
</el-col>
<!-- 会话详情选中会话的消息列表 -->
<el-col :span="10">
<ContentWrap>
<KeFuMessageList ref="keFuChatBoxRef" @change="getConversationList" />
</ContentWrap>
</el-col>
<!-- 会员足迹选中会话的会员足迹 -->
<el-col :span="5">
<ContentWrap>
<MemberBrowsingHistory ref="memberBrowsingHistoryRef" />
</ContentWrap>
</el-col>
</el-row>
<div>
<!-- 新区域放在头部 -->
<el-row style="display: flex; justify-content: center;">
<el-col :span="24">
<div style="width:100%;height:70px;background-color:#3c80ff;">
<el-row>
<el-col :span="6">
<el-input
style="width: 80%;margin-top: 20px;margin-left:10px;"
:suffix-icon="Search"
/>
</el-col>
<el-col :span="6">
<span style="display: flex; margin-top: 15px;">
<el-avatar
:src="pic"
/>
<span style="margin-left:5px;margin-top: 9px;">{{name}}</span>
<!-- <el-switch
style="margin-top: 4px;--el-switch-on-color: #13ce66; --el-switch-off-color: #b6bac1;"
v-model="value6"
class="ml-2"
width="60"
inline-prompt
active-text="在线"
inactive-text="下线"
/> -->
</span>
</el-col>
<el-col :span="6">
<el-button @click="out" size="small" round style="margin-top:23px;margin-left:75%">退出</el-button>
</el-col>
<el-col :span="6">
<el-menu
background-color="#3c80ff"
text-color="white"
active-text-color="white"
style="width:100%;display: flex;"
>
<el-menu-item @click="userInfo" style="width:33%;height:70px" index="1">客户信息</el-menu-item>
<el-menu-item @click="zuoji" style="width:33%;height:70px" index="2">他的足迹</el-menu-item>
<!-- <el-menu-item style="width:34%;height:70px" index="3">商品信息</el-menu-item> -->
</el-menu>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
<!-- 原有的三个区域 -->
<el-row style="display: flex; justify-content: center;">
<!-- 会话列表 -->
<el-col :span="6">
<ContentWrap>
<KeFuConversationList ref="keFuConversationRef" @change="handleChange"/>
</ContentWrap>
</el-col>
<!-- 会话详情选中会话的消息列表 -->
<el-col :span="12">
<ContentWrap>
<KeFuMessageList ref="keFuChatBoxRef" @change="getConversationList"/>
</ContentWrap>
</el-col>
<!-- 会员足迹选中会话的会员足迹 -->
<el-col :span="6">
<ContentWrap v-show="chick == '2'">
<MemberBrowsingHistory ref="memberBrowsingHistoryRef"/>
</ContentWrap>
<ContentWrap v-show = "chick == '1'">
<div style="height: 522px ;" >
<div>
<span style="display: flex;">
<el-avatar
style="border: 1px solid #f8f9ee;"
:src="user.avatar"
/>
<span style="margin-left:5px;margin-top: 9px;">{{user.nickname}}</span>
</span>
</div>
<el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;" />
<div>
<span style="color: #5d5d59;font-size: 13px ;">手机号</span><span style="margin-left: 47px;font-size: 14px ;">{{user.mobile}}</span>
</div>
<div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">分组</span><span style="margin-left: 60px;font-size: 14px ;">{{user.groupName}}</span>
</div>
<div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">用户标签</span><span style="margin-left: 33px;font-size: 14px ;">小客户</span>
</div>
<el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;" />
<div>
<span style="color: #5d5d59;font-size: 13px ;">用户等级</span><span style="margin-left: 35px;font-size: 14px ;">{{user.levelName}}</span>
</div>
<div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">推荐人</span><span style="margin-left: 47px;font-size: 14px ;">客户</span>
</div>
<div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">用户类型</span><span style="margin-left: 33px;font-size: 14px ;">小客户</span>
</div>
<div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">积分</span><span style="margin-left: 60px;font-size: 14px ;">{{user.point}}</span>
</div>
<div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">推广员</span><span style="margin-left: 47px;font-size: 14px ;">客户</span>
</div>
<div style="margin-top: 5px;">
<span style="color: #5d5d59;font-size: 13px ;">生日</span><span style="margin-left: 60px;font-size: 14px ;">{{user.birthday}}</span>
</div>
<el-divider style="border-color: #f5f5f5;margin-top: 15px;margin-bottom:15px;" />
</div>
</ContentWrap>
</el-col>
</el-row>
</div>
</template>
<script lang="ts" setup>
import { KeFuConversationList, KeFuMessageList, MemberBrowsingHistory } from './components'
import { WebSocketMessageTypeConstants } from './components/tools/constants'
import { KeFuConversationRespVO } from '@/api/mall/promotion/kefu/conversation'
import { getRefreshToken, getAccessToken } from '@/utils/auth'
import { useWebSocket } from '@vueuse/core'
import { KeFuConversationList, KeFuMessageList,MemberBrowsingHistory,UserInfo } from './components'
import {WebSocketMessageTypeConstants} from './components/tools/constants'
import {KeFuConversationRespVO} from '@/api/mall/promotion/kefu/conversation'
import {getRefreshToken, getAccessToken} from '@/utils/auth'
import {useWebSocket} from '@vueuse/core'
import {Search} from '@element-plus/icons-vue'
import * as UserApi from '@/api/member/user'
defineOptions({ name: 'KeFu' })
defineOptions({name: 'KeFu'})
const value6 = ref(true)
const message = useMessage() //
const params = new URLSearchParams(window.location.search);
const name = params.get('name');
const pic = params.get('pic');
const conversations = ref<KeFuConversationRespVO[]>([])
// const userInfoRef = ref<InstanceType<typeof UserInfo>>()
const user = ref<UserApi.UserVO>({} as UserApi.UserVO)
const userId = ref(0)
// ======================= WebSocket start =======================
const server = ref(
(import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws') +
'?token=' +
getRefreshToken() // 使 getRefreshToken() 使 getAccessToken() WebSocket 便访
) // WebSocket
const server = ref(
(import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws') +
'?token=' +
getRefreshToken()
// 使 getRefreshToken() 使 getAccessToken() WebSocket 便访
) // WebSocket
/** 发起 WebSocket 连接 */
const { data, close, open } = useWebSocket(server.value, {
autoReconnect: false,
heartbeat: true
})
let a = 0;
const {status, data, send, open, close} = useWebSocket(server.value, {
onConnected: function (ws) {
console.log('websocket 连接成功!', ws);
},
onDisconnected: function (ws, event) {
console.log('WebSocket 连接断开', event);
},
onError: function (ws, event) {
console.error('WebSocket 连接错误:', event);
if (event instanceof ErrorEvent) {
console.error('详细错误信息:', event.message);
} else {
console.error('非标准错误:', event);
}
},
onMessage: function (ws, event) {
console.log('收到的 WebSocket 消息:', event.data);
a = a + 1 ;
if(a == 2){
getConversationList()
keFuChatBoxRef.value?.refreshMessageList()
a = 0;
}
},
autoReconnect: false, //
heartbeat: true
});
/** 监听 WebSocket 数据 */
watchEffect(() => {
console.log('连接服务器得到消息:',data.value)
if (!data.value) {
return
}
@ -56,10 +209,12 @@
if (data.value === 'pong') {
return
}
// 2.1 type
const jsonMessage = JSON.parse(data.value)
const type = jsonMessage.type
console.log('来自用户发送的消息:',data.value)
if (!type) {
message.error('未知的消息类型:' + data.value)
return
@ -83,6 +238,8 @@
console.error(error)
}
})
// ======================= WebSocket end =======================
/** 加载会话列表 */
const keFuConversationRef = ref<InstanceType<typeof KeFuConversationList>>()
@ -94,21 +251,43 @@
const keFuChatBoxRef = ref<InstanceType<typeof KeFuMessageList>>()
const memberBrowsingHistoryRef = ref<InstanceType<typeof MemberBrowsingHistory>>()
const handleChange = (conversation: KeFuConversationRespVO) => {
conversations.value = conversation
chick.value = '2'
userId.value = conversation.userId
keFuChatBoxRef.value?.getNewMessageList(conversation)
memberBrowsingHistoryRef.value?.initHistory(conversation)
}
const out = () =>{
window.close();
// window.location.href = '/kefu/support-staff';
}
const chick = ref('2')
const userInfo = async () =>{
chick.value = '1'
user.value = await UserApi.getUserInfo(userId.value)
}
const zuoji = () =>{
chick.value = '2'
// keFuChatBoxRef.value?.getNewMessageList(conversations.value)
memberBrowsingHistoryRef.value?.initHistory(conversations.value)
}
/** 初始化 */
onMounted(() => {
getConversationList()
// websocket
open()
console.log('WebSocket 已初始化');
})
/** 销毁 */
onBeforeUnmount(() => {
// websocket
close()
console.log('WebSocket 已关闭');
})
</script>
@ -116,6 +295,7 @@
.kefu {
height: calc(100vh - 165px);
overflow: auto; /* 确保内容可滚动 */
}
/* 定义滚动条样式 */
@ -133,8 +313,10 @@
/* 定义滑块 内阴影+圆角 */
::-webkit-scrollbar-thumb {
border-radius: 10px;
box-shadow: inset 0 0 0 rgba(240, 240, 240, 0.5);
background-color: rgba(240, 240, 240, 0.5);
}
</style>

View File

@ -26,7 +26,7 @@
class="!w-240px"
/>
</el-form-item>
<el-form-item label="登录账号" prop="account">
<!-- <el-form-item label="登录账号" prop="account">
<el-input
v-model="queryParams.account"
placeholder="请输入登录账号"
@ -34,15 +34,6 @@
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<!-- <el-form-item label="登录密码" prop="password">
<el-input
v-model="queryParams.password"
placeholder="请输入登录密码"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item> -->
<el-form-item label="客服状态" prop="status">
<el-select
@ -59,36 +50,6 @@
/>
</el-select>
</el-form-item>
<!-- <el-form-item label="手机订单管理" prop="orderManage">
<el-select
v-model="queryParams.orderManage"
placeholder="请选择手机订单管理"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.KEFU_SUPPORT_STAFF_ORDER_MANAGE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item> -->
<!-- <el-form-item label="订单通知" prop="orderInform">
<el-select
v-model="queryParams.orderInform"
placeholder="请选择订单通知"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.KEFU_SUPPORT_STAFF_ORDER_INFORM)"
: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"
@ -109,7 +70,7 @@
@click="openForm('create')"
v-hasPermi="['promotion:support-staff:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
<Icon icon="ep:plus" class="mr-5px" /> 新增客服
</el-button>
<el-button
type="success"
@ -143,22 +104,11 @@
</el-table-column>
<el-table-column label="手机号码" align="center" prop="phone" />
<el-table-column label="登录账号" align="center" prop="account" />
<!-- <el-table-column label="登录密码" align="center" prop="password" /> -->
<el-table-column label="客服状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.KEFU_SUPPORT_STAFF_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<!-- <el-table-column label="手机订单管理" align="center" prop="orderManage">
<template #default="scope">
<dict-tag :type="DICT_TYPE.KEFU_SUPPORT_STAFF_ORDER_MANAGE" :value="scope.row.orderManage" />
</template>
</el-table-column>
<el-table-column label="订单通知" align="center" prop="orderInform">
<template #default="scope">
<dict-tag :type="DICT_TYPE.KEFU_SUPPORT_STAFF_ORDER_INFORM" :value="scope.row.orderInform" />
</template>
</el-table-column> -->
<el-table-column
label="创建时间"
align="center"
@ -188,7 +138,7 @@
v-if="scope.row.status == 1"
link
type="success"
@click="handleEnterConsole(scope.row.id)"
@click="handleEnterConsole(scope.row.id,scope.row.name,scope.row.pic)"
>
进入工作台
</el-button>
@ -206,6 +156,9 @@
<!-- 表单弹窗添加/修改 -->
<SupportStaffForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
@ -215,6 +168,7 @@ import download from '@/utils/download'
import { SupportStaffApi, SupportStaffVO } from '@/api/mall/promotion/supportstaff'
import SupportStaffForm from './SupportStaffForm.vue'
import { setStaffToken} from '@/utils/auth'
import { createImageViewer } from '@/components/ImageViewer'
/** 客服人员 列表 */
defineOptions({ name: 'SupportStaff' })
@ -271,6 +225,13 @@ const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
const imagePreview = (imgUrl: string) => {
createImageViewer({
zIndex: 9999999,
urlList: [imgUrl]
})
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
@ -284,9 +245,10 @@ const handleDelete = async (id: number) => {
} catch {}
}
/** 客服进入工作台 */
const handleEnterConsole = async (id: number) => {
const handleEnterConsole = async (id: number,name: string,pic: string) => {
setStaffToken(id);
window.open(`${window.location.origin}/kefu/kefu`, '_blank');
const url = `${window.location.origin}/kefu/kefu?name=${encodeURIComponent(name)}&pic=${encodeURIComponent(pic)}`;
window.open(url, '_blank');
}
/** 导出按钮操作 */
const handleExport = async () => {

View File

@ -25,7 +25,7 @@
:key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item> -->
<el-form-item label="标题" prop="title">
<!-- <el-form-item label="标题" prop="title">
<el-input v-model="queryParams.title" placeholder="请输入标题" clearable @keyup.enter="handleQuery"
class="!w-240px" />
</el-form-item>
@ -33,22 +33,22 @@
<el-date-picker 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')]" class="!w-240px" />
</el-form-item>
</el-form-item> -->
<el-form-item>
<el-button @click="handleQuery">
<!-- <el-button @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> 搜索
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> 重置
</el-button>
</el-button> -->
<el-button type="primary" plain @click="openForm('create')"
v-hasPermi="['promotion:verbal-trick:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新增
<Icon icon="ep:plus" class="mr-5px" /> 添加话术
</el-button>
<el-button type="success" plain @click="handleExport" :loading="exportLoading"
<!-- <el-button type="success" plain @click="handleExport" :loading="exportLoading"
v-hasPermi="['promotion:verbal-trick:export']">
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-button> -->
</el-form-item>
</el-form>
</ContentWrap>

View File

@ -0,0 +1,181 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="钱包编号" prop="walletId">
<el-input v-model="formData.walletId" placeholder="请输入钱包编号" />
</el-form-item>
<el-form-item label="充值实际到账" prop="totalPrice">
<el-input v-model="formData.totalPrice" placeholder="请输入充值实际到账" />
</el-form-item>
<el-form-item label="实际支付金额" prop="payPrice">
<el-input v-model="formData.payPrice" placeholder="请输入实际支付金额" />
</el-form-item>
<el-form-item label="钱包赠送金额" prop="bonusPrice">
<el-input v-model="formData.bonusPrice" placeholder="请输入钱包赠送金额" />
</el-form-item>
<el-form-item label="充值套餐编号" prop="packageId">
<el-input v-model="formData.packageId" placeholder="请输入充值套餐编号" />
</el-form-item>
<el-form-item label="是否支付" prop="payStatus">
<el-select v-model="formData.payStatus" placeholder="请选择是否支付">
<el-option
v-for="dict in getBoolDictOptions(DICT_TYPE.PAY_WALLET_RECHARGE_PAY_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="支付订单编号" prop="payOrderId">
<el-input v-model="formData.payOrderId" placeholder="请输入支付订单编号" />
</el-form-item>
<el-form-item label="支付成功的支付渠道" prop="payChannelCode">
<el-input v-model="formData.payChannelCode" placeholder="请输入支付成功的支付渠道" />
</el-form-item>
<el-form-item label="订单支付时间" prop="payTime">
<el-date-picker
v-model="formData.payTime"
type="date"
value-format="x"
placeholder="选择订单支付时间"
/>
</el-form-item>
<el-form-item label="支付退款单编号" prop="payRefundId">
<el-input v-model="formData.payRefundId" placeholder="请输入支付退款单编号" />
</el-form-item>
<el-form-item label="退款金额(包含赠送金额)" prop="refundTotalPrice">
<el-input v-model="formData.refundTotalPrice" placeholder="请输入退款金额(包含赠送金额)" />
</el-form-item>
<el-form-item label="退款支付金额" prop="refundPayPrice">
<el-input v-model="formData.refundPayPrice" placeholder="请输入退款支付金额" />
</el-form-item>
<el-form-item label="退款钱包赠送金额" prop="refundBonusPrice">
<el-input v-model="formData.refundBonusPrice" placeholder="请输入退款钱包赠送金额" />
</el-form-item>
<el-form-item label="退款时间" prop="refundTime">
<el-date-picker
v-model="formData.refundTime"
type="date"
value-format="x"
placeholder="选择退款时间"
/>
</el-form-item>
<el-form-item label="退款状态" prop="refundStatus">
<el-radio-group v-model="formData.refundStatus">
<el-radio label="1">请选择字典生成</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
import { WalletRechargeApi, WalletRechargeVO } from '@/api/pay/wallet/recharge'
/** 钱包充值 表单 */
defineOptions({ name: 'WalletRechargeForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
walletId: undefined,
totalPrice: undefined,
payPrice: undefined,
bonusPrice: undefined,
packageId: undefined,
payStatus: undefined,
payOrderId: undefined,
payChannelCode: undefined,
payTime: undefined,
payRefundId: undefined,
refundTotalPrice: undefined,
refundPayPrice: undefined,
refundBonusPrice: undefined,
refundTime: undefined,
refundStatus: undefined
})
const formRules = reactive({
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await WalletRechargeApi.getWalletRecharge(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as WalletRechargeVO
if (formType.value === 'create') {
await WalletRechargeApi.createWalletRecharge(data)
message.success(t('common.createSuccess'))
} else {
await WalletRechargeApi.updateWalletRecharge(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
walletId: undefined,
totalPrice: undefined,
payPrice: undefined,
bonusPrice: undefined,
packageId: undefined,
payStatus: undefined,
payOrderId: undefined,
payChannelCode: undefined,
payTime: undefined,
payRefundId: undefined,
refundTotalPrice: undefined,
refundPayPrice: undefined,
refundBonusPrice: undefined,
refundTime: undefined,
refundStatus: undefined
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,280 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="100px"
>
<el-form-item label="是否支付" prop="payStatus">
<el-select
v-model="queryParams.payStatus"
placeholder="请选择是否支付"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getBoolDictOptions(DICT_TYPE.PAY_WALLET_RECHARGE_PAY_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="支付渠道" prop="payChannelCode">
<el-input
v-model="queryParams.payChannelCode"
placeholder="请输入支付渠道"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="订单支付时间" prop="payTime">
<el-date-picker
v-model="queryParams.payTime"
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')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
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')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<!-- <el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['pay:wallet-recharge:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['pay:wallet-recharge:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button> -->
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="ID" align="center" prop="id" />
<el-table-column label="用户昵称" align="center" prop="name" />
<!-- <el-table-column label="用户头像" align="center" prop="avatar" /> -->
<el-table-column label="用户头像" align="center" prop="avatar" >
<template #default="{ row }">
<div class="flex justify-center items-center">
<el-image
fit="cover"
:src="row.avatar"
class="flex-none w-50px h-50px"
@click="imagePreview(row.avatar)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="支付订单编号" align="center" prop="payOrderId" />
<el-table-column label="支付金额" align="center" prop="payPrice" />
<el-table-column label="支付渠道" align="center" prop="payChannelCode" />
<!-- <el-table-column label="钱包赠送金额" align="center" prop="bonusPrice" />
<el-table-column label="充值套餐编号" align="center" prop="packageId" /> -->
<el-table-column label="是否支付" align="center" prop="payStatus">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PAY_WALLET_RECHARGE_PAY_STATUS" :value="scope.row.payStatus" />
</template>
</el-table-column>
<el-table-column
label="订单支付时间"
align="center"
prop="payTime"
:formatter="dateFormatter"
width="180px"
/>
<!-- <el-table-column label="支付退款单编号" align="center" prop="payRefundId" />
<el-table-column label="退款金额(包含赠送金额)" align="center" prop="refundTotalPrice" />
<el-table-column label="退款支付金额" align="center" prop="refundPayPrice" />
<el-table-column label="退款钱包赠送金额" align="center" prop="refundBonusPrice" />
<el-table-column
label="退款时间"
align="center"
prop="refundTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="退款状态" align="center" prop="refundStatus" /> -->
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<!-- <el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['pay:wallet-recharge:update']"
>
编辑
</el-button> -->
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['pay:wallet-recharge:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<WalletRechargeForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { WalletRechargeApi, WalletRechargeVO } from '@/api/pay/wallet/recharge'
import WalletRechargeForm from './WalletRechargeForm.vue'
import { createImageViewer } from "@/components/ImageViewer"
/** 钱包充值 列表 */
defineOptions({ name: 'WalletRecharge' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<WalletRechargeVO[]>([]) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
walletId: undefined,
totalPrice: undefined,
payPrice: undefined,
bonusPrice: undefined,
packageId: undefined,
payStatus: undefined,
payOrderId: undefined,
payChannelCode: undefined,
payTime: [],
payRefundId: undefined,
refundTotalPrice: undefined,
refundPayPrice: undefined,
refundBonusPrice: undefined,
refundTime: [],
refundStatus: undefined,
createTime: []
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await WalletRechargeApi.getWalletRechargePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
const imagePreview = (imgUrl : string) => {
createImageViewer({
urlList: [imgUrl]
})
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await WalletRechargeApi.deleteWalletRecharge(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await WalletRechargeApi.exportWalletRecharge(queryParams)
download.excel(data, '钱包充值.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -29,6 +29,6 @@ public class WebSocketProperties {
* 可选值localredisrocketmqkafkarabbitmq
*/
@NotNull(message = "WebSocket 的消息发送者不能为空")
private String senderType = "local";
private String senderType = "redis";
}

View File

@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
import cn.iocoder.yudao.framework.websocket.core.handler.JsonWebSocketMessageHandler;
import cn.iocoder.yudao.framework.websocket.core.listener.WebSocketMessageListener;
import cn.iocoder.yudao.framework.websocket.core.security.LoginUserHandshakeInterceptor;
import cn.iocoder.yudao.framework.websocket.core.security.WebSocketAuthorizeRequestsCustomizer;
import cn.iocoder.yudao.framework.websocket.core.sender.kafka.KafkaWebSocketMessageConsumer;
import cn.iocoder.yudao.framework.websocket.core.sender.kafka.KafkaWebSocketMessageSender;
import cn.iocoder.yudao.framework.websocket.core.sender.local.LocalWebSocketMessageSender;
@ -76,10 +77,15 @@ public class YudaoWebSocketAutoConfiguration {
return new WebSocketSessionManagerImpl();
}
@Bean
public WebSocketAuthorizeRequestsCustomizer webSocketAuthorizeRequestsCustomizer(WebSocketProperties webSocketProperties) {
return new WebSocketAuthorizeRequestsCustomizer(webSocketProperties);
}
// ==================== Sender 相关 ====================
@Configuration
@ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "local", matchIfMissing = true)
@ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "local")
public class LocalWebSocketMessageSenderConfiguration {
@Bean
@ -90,7 +96,7 @@ public class YudaoWebSocketAutoConfiguration {
}
@Configuration
@ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "redis", matchIfMissing = true)
@ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "redis")
public class RedisWebSocketMessageSenderConfiguration {
@Bean
@ -108,7 +114,7 @@ public class YudaoWebSocketAutoConfiguration {
}
@Configuration
@ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "rocketmq", matchIfMissing = true)
@ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "rocketmq")
public class RocketMQWebSocketMessageSenderConfiguration {
@Bean
@ -127,7 +133,7 @@ public class YudaoWebSocketAutoConfiguration {
}
@Configuration
@ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "rabbitmq", matchIfMissing = true)
@ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "rabbitmq")
public class RabbitMQWebSocketMessageSenderConfiguration {
@Bean
@ -156,7 +162,7 @@ public class YudaoWebSocketAutoConfiguration {
}
@Configuration
@ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "kafka", matchIfMissing = true)
@ConditionalOnProperty(prefix = "yudao.websocket", name = "sender-type", havingValue = "kafka")
public class KafkaWebSocketMessageSenderConfiguration {
@Bean

View File

@ -21,4 +21,4 @@ public class WebSocketAuthorizeRequestsCustomizer extends AuthorizeRequestsCusto
registry.antMatchers(webSocketProperties.getPath()).permitAll();
}
}
}

View File

@ -43,7 +43,7 @@ public interface WebSocketSenderApi {
send(userType, userId, messageType, JsonUtils.toJsonString(messageContent));
}
default void sendObject(Integer userType, String messageType, Object messageContent) {
default void sendObject(Integer userType, String messageType, Object messageContent) { //用户发送消息
send(userType, messageType, JsonUtils.toJsonString(messageContent));
}

View File

@ -79,6 +79,12 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-system-biz</artifactId>
<version>2.1.0-jdk8-snapshot</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -84,9 +84,9 @@ public class KeFuConversationController {
}
@Operation(summary = "转接会话给指定客服")
@GetMapping("/transfer/{id}/{kefuId}")
public CommonResult<Boolean> transferConversation(@PathVariable("id") Long id, @PathVariable("kefuId") Long kefuId) {
conversationService.transferConversation(id, kefuId);
public CommonResult<String> transferConversation(@PathVariable("id") Long id, @PathVariable("kefuId") Long kefuId) {
String name = conversationService.transferConversation(id, kefuId);
// 处理逻辑
return success(true);
return success(name);
}
}

View File

@ -28,4 +28,8 @@ public class AppDiyTemplatePropertyRespVO {
@JsonRawValue
private String user;
private String goodsType;
private String themeType;
}

View File

@ -5,6 +5,8 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageRespVO;
import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessagePageReqVO;
import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO;
@ -31,6 +33,10 @@ public class AppKeFuMessageController {
@Resource
private KeFuMessageService kefuMessageService;
@Resource
private MemberUserApi memberUserApi;
@PostMapping("/send")
@Operation(summary = "发送客服消息")
@PreAuthenticated
@ -53,6 +59,19 @@ public class AppKeFuMessageController {
@PreAuthenticated
public CommonResult<PageResult<KeFuMessageRespVO>> getKefuMessagePage(@Valid AppKeFuMessagePageReqVO pageReqVO) {
PageResult<KeFuMessageDO> pageResult = kefuMessageService.getKeFuMessagePage(pageReqVO, getLoginUserId());
for (int i = 0; i < pageResult.getList().size(); i++) {
KeFuMessageDO keFuMessageDO = pageResult.getList().get(i);
if (keFuMessageDO.getSenderType() == 1){
MemberUserRespDTO user = memberUserApi.getUser(keFuMessageDO.getSenderId());
keFuMessageDO.setSenderAvatar(user.getAvatar());
}
if (keFuMessageDO.getSenderType() == 2){
String systemUserAvatar = kefuMessageService.findSystemUserAvatar(keFuMessageDO.getSenderId());
keFuMessageDO.setSenderAvatar(systemUserAvatar);
}
}
return success(BeanUtils.toBean(pageResult, KeFuMessageRespVO.class));
}

View File

@ -61,4 +61,11 @@ public class DiyTemplateDO extends BaseDO {
*/
private String property;
@TableField(exist = false)
private String goodsType;
@TableField(exist = false)
private String themeType;
}

View File

@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.promotion.enums.kefu.KeFuMessageContentTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
@ -78,4 +79,7 @@ public class KeFuMessageDO extends BaseDO {
*/
private Boolean readStatus;
@TableField(exist = false)
private String senderAvatar;
}

View File

@ -19,13 +19,13 @@ public interface KeFuConversationMapper extends BaseMapperX<KeFuConversationDO>
default List<KeFuConversationDO> selectConversationList() {
return selectList(new LambdaQueryWrapperX<KeFuConversationDO>()
.eq(KeFuConversationDO::getAdminDeleted, Boolean.FALSE)
.orderByDesc(KeFuConversationDO::getCreateTime));
.orderByDesc(KeFuConversationDO::getLastMessageTime));
}
default List<KeFuConversationDO> selectConversationList(Long kefuId) {
return selectList(new LambdaQueryWrapperX<KeFuConversationDO>()
.eq(KeFuConversationDO::getAdminDeleted, Boolean.FALSE)
.eqIfPresent(KeFuConversationDO::getKefuId, kefuId)
.orderByDesc(KeFuConversationDO::getCreateTime));
.orderByDesc(KeFuConversationDO::getLastMessageTime));
}
default void updateAdminUnreadMessageCountIncrement(Long id) {

View File

@ -9,6 +9,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.Collection;
import java.util.List;
@ -46,4 +47,8 @@ public interface KeFuMessageMapper extends BaseMapperX<KeFuMessageDO> {
.orderByDesc(KeFuMessageDO::getCreateTime));
}
@Select(" SELECT avatar FROM system_users where id = #{id} ")
String findSystemUserAvatar(Long id);
}

View File

@ -26,7 +26,7 @@ public interface SupportStaffMapper extends BaseMapperX<SupportStaffDO> {
.eqIfPresent(SupportStaffDO::getOrderManage, reqVO.getOrderManage())
.eqIfPresent(SupportStaffDO::getOrderInform, reqVO.getOrderInform())
.betweenIfPresent(SupportStaffDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(SupportStaffDO::getId));
.orderByAsc(SupportStaffDO::getId));
}
}

View File

@ -11,12 +11,15 @@ import cn.iocoder.yudao.module.promotion.convert.diy.DiyPageConvert;
import cn.iocoder.yudao.module.promotion.convert.diy.DiyTemplateConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.diy.DiyTemplateDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.diy.DiyTemplateMapper;
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
@ -36,6 +39,9 @@ public class DiyTemplateServiceImpl implements DiyTemplateService {
@Resource
private DiyPageService diyPageService;
@Resource
private DictDataApi dictDataApi;
@Transactional(rollbackFor = Exception.class)
@Override
public Long createDiyTemplate(DiyTemplateCreateReqVO createReqVO) {
@ -165,7 +171,17 @@ public class DiyTemplateServiceImpl implements DiyTemplateService {
@Override
public DiyTemplateDO getUsedDiyTemplate() {
return diyTemplateMapper.selectByUsed(true);
DiyTemplateDO diyTemplateDO = diyTemplateMapper.selectByUsed(true);
List<DictDataRespDTO> dictDataList = dictDataApi.getDictDataList("diy-template-theme");
DictDataRespDTO dictDataRespDTO = dictDataList.get(0);
diyTemplateDO.setThemeType(dictDataRespDTO.getValue());
List<DictDataRespDTO> dictDataList1 = dictDataApi.getDictDataList("diy-template-goods");
DictDataRespDTO dictDataRespDTO1 = dictDataList1.get(0);
diyTemplateDO.setGoodsType(dictDataRespDTO1.getValue());
return diyTemplateDO;
}
}

View File

@ -102,5 +102,5 @@ public interface KeFuConversationService {
* @param kefuId 客服id
* @return void
*/
void transferConversation(Long id, Long kefuId);
String transferConversation(Long id, Long kefuId);
}

View File

@ -5,7 +5,9 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationUpdatePinnedReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.supportstaff.SupportStaffDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.kefu.KeFuConversationMapper;
import cn.iocoder.yudao.module.promotion.dal.mysql.supportstaff.SupportStaffMapper;
import cn.iocoder.yudao.module.promotion.enums.kefu.KeFuMessageContentTypeEnum;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -30,6 +32,9 @@ public class KeFuConversationServiceImpl implements KeFuConversationService {
@Resource
private KeFuConversationMapper conversationMapper;
@Resource
private SupportStaffMapper supportStaffMapper;
@Override
public void deleteKefuConversation(Long id) {
// 校验存在
@ -126,11 +131,13 @@ public class KeFuConversationServiceImpl implements KeFuConversationService {
}
@Override
public void transferConversation(Long id, Long kefuId) {
public String transferConversation(Long id, Long kefuId) {
KeFuConversationDO keFuConversationDO = new KeFuConversationDO();
keFuConversationDO.setId(id);
keFuConversationDO.setKefuId(kefuId);
conversationMapper.updateById(keFuConversationDO);
SupportStaffDO supportStaffDO = supportStaffMapper.selectById(kefuId);
return supportStaffDO.getName();
}
}

View File

@ -31,6 +31,8 @@ public interface KeFuMessageService {
* @return 编号
*/
Long sendKefuMessage(AppKeFuMessageSendReqVO sendReqVO);
String sendKefuMessageTest(String s);
/**
@ -59,4 +61,6 @@ public interface KeFuMessageService {
*/
PageResult<KeFuMessageDO> getKeFuMessagePage(AppKeFuMessagePageReqVO pageReqVO, Long userId);
String findSystemUserAvatar(Long id);
}

View File

@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO;
import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessagePageReqVO;
@ -16,7 +17,10 @@ import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuM
import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.kefu.KeFuMessageMapper;
import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyMessageDO;
import cn.iocoder.yudao.module.system.dal.mysql.notify.NotifyMessageMapper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -51,9 +55,12 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
@Resource
private WebSocketSenderApi webSocketSenderApi;
@Resource
private NotifyMessageMapper notifyMessageMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long sendKefuMessage(KeFuMessageSendReqVO sendReqVO) {
public Long sendKefuMessage(KeFuMessageSendReqVO sendReqVO) { //客服发消息
// 1.1 校验会话是否存在
KeFuConversationDO conversation = conversationService.validateKefuConversationExists(sendReqVO.getConversationId());
// 1.2 校验接收人是否存在
@ -74,20 +81,39 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
}
@Override
public Long sendKefuMessage(AppKeFuMessageSendReqVO sendReqVO) {
public Long sendKefuMessage(AppKeFuMessageSendReqVO sendReqVO) { //用户发消息
// 1.1 设置会话编号
KeFuMessageDO kefuMessage = BeanUtils.toBean(sendReqVO, KeFuMessageDO.class);
KeFuConversationDO conversation = conversationService.getOrCreateConversation(sendReqVO.getSenderId());
kefuMessage.setConversationId(conversation.getId());
// 1.2 保存消息
kefuMessage.setReceiverId(conversation.getKefuId()).setReceiverType(UserTypeEnum.ADMIN.getValue()); // 设置接收人
keFuMessageMapper.insert(kefuMessage);
// 2. 更新会话消息冗余
conversationService.updateConversationLastMessage(kefuMessage);
getSelf().sendAsyncMessageToMembers(conversation.getKefuId(), KEFU_MESSAGE_TYPE, kefuMessage);
// 3. 通知所有管理员更新对话
getSelf().sendAsyncMessageToAdmin(KEFU_MESSAGE_TYPE, kefuMessage);
getSelf().sendAsyncMessageToAdmins(KEFU_MESSAGE_TYPE, kefuMessage);
return kefuMessage.getId();
}
//添加站内信
// MemberUserRespDTO user = memberUserApi.getUser(sendReqVO.getSenderId());
// NotifyMessageDO notifyMessageDO = new NotifyMessageDO()
// .setUserId(sendReqVO.getSenderId())
// .setUserType(sendReqVO.getSenderType())
// .setTemplateId((long)1)
// .setTemplateCode()
// .setTemplateNickname(user.getNickname())
// .setTemplateContent("客户发来消息")
// .setTemplateType(2)
// .setTemplateParams()
// .setReadStatus(2);
// notifyMessageMapper.insert(notifyMessageDO);
@Override
public String sendKefuMessageTest(String s){
webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), 1L, "1", s);;
@ -137,11 +163,21 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
webSocketSenderApi.sendObject(UserTypeEnum.MEMBER.getValue(), userId, messageType, content);
}
@Async
public void sendAsyncMessageToMembers(Long userId, String messageType, Object content) {
webSocketSenderApi.sendObject(UserTypeEnum.MEMBER.getValue(), userId, messageType, content);
}
@Async
public void sendAsyncMessageToAdmin(String messageType, Object content) {
webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), messageType, content);
}
@Async
public void sendAsyncMessageToAdmins(String messageType, Object content) {
webSocketSenderApi.sendObject(UserTypeEnum.MEMBER.getValue(), messageType, content);
}
@Override
public PageResult<KeFuMessageDO> getKeFuMessagePage(KeFuMessagePageReqVO pageReqVO) {
return keFuMessageMapper.selectPage(pageReqVO);
@ -159,6 +195,11 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
return keFuMessageMapper.selectPage(pageReqVO);
}
@Override
public String findSystemUserAvatar(Long id) {
return keFuMessageMapper.findSystemUserAvatar(id);
}
private KeFuMessageServiceImpl getSelf() {
return SpringUtil.getBean(getClass());
}

View File

@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;

View File

@ -17,7 +17,10 @@ import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
import cn.iocoder.yudao.module.member.dal.dataobject.memberCode.MemberCodeDo;
import cn.iocoder.yudao.module.member.dal.dataobject.tag.MemberTagDO;
import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
import cn.iocoder.yudao.module.member.dal.mysql.group.MemberGroupMapper;
import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelMapper;
import cn.iocoder.yudao.module.member.dal.mysql.memberCode.MemberCodeMapper;
import cn.iocoder.yudao.module.member.dal.mysql.tag.MemberTagMapper;
import cn.iocoder.yudao.module.member.enums.point.MemberPointBizTypeEnum;
import cn.iocoder.yudao.module.member.service.clubCard.ClubCardService;
import cn.iocoder.yudao.module.member.service.group.MemberGroupService;
@ -52,6 +55,15 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLogi
@Validated
public class MemberUserController {
@Resource
private MemberLevelMapper memberLevelMapper;
@Resource
private MemberGroupMapper memberGroupMapper;
@Resource
private MemberTagMapper memberTagMapper;
@Resource
private MemberUserService memberUserService;
@Resource
@ -169,4 +181,23 @@ public class MemberUserController {
memberCodeMapper.insert(memberCodeDo);
return success(uuid);
}
@GetMapping("/getUserInfo")
public CommonResult<MemberUserDO> getUserInfo(Long id){
MemberUserDO user = memberUserService.getUser(id);
if (user.getGroupId() != null){
MemberGroupDO groupDO = memberGroupMapper.selectOne("id", user.getGroupId());
user.setGroupName(groupDO.getName());
}
if (user.getLevelId() != null && user.getLevelId() != 0){
MemberLevelDO levelDO = memberLevelMapper.selectOne("id", user.getLevelId());
user.setLevelName(levelDO.getName());
}
return success(user);
}
}

View File

@ -131,6 +131,9 @@ public class MemberUserDO extends TenantBaseDO {
* 关联 {@link MemberLevelDO#getId()} 字段
*/
private Long levelId;
@TableField(exist = false)
private String levelName;
/**
* 会员经验
*/
@ -142,6 +145,9 @@ public class MemberUserDO extends TenantBaseDO {
*/
private Long groupId;
@TableField(exist = false)
private String groupName;
/**
* 是否绑过卡,是否开通过会员(0未开通,1试用,2有效期,3永久,4过期)
*/

View File

@ -1,13 +1,20 @@
package cn.iocoder.yudao.module.pay.controller.admin.wallet;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO;
import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.recharge.WalletRechargePageReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.recharge.WalletRechargeRespVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.WalletRechargeDO;
import cn.iocoder.yudao.module.pay.service.wallet.PayWalletRechargeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -55,4 +62,12 @@ public class PayWalletRechargeController {
return success(true);
}
@GetMapping("/page")
@Operation(summary = "获得钱包充值分页")
@PreAuthorize("@ss.hasPermission('pay:wallet-recharge:query')")
public CommonResult<PageResult<WalletRechargeRespVO>> getWalletRechargePage(@Valid WalletRechargePageReqVO pageReqVO) {
PageResult<PayWalletRechargeDO> pageResult = walletRechargeService.getWalletRechargePage(pageReqVO);
return success(BeanUtils.toBean(pageResult, WalletRechargeRespVO.class));
}
}

View File

@ -0,0 +1,69 @@
package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.recharge;
import lombok.*;
import java.util.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 钱包充值分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class WalletRechargePageReqVO extends PageParam {
@Schema(description = "钱包编号", example = "3219")
private Long walletId;
@Schema(description = "充值实际到账", example = "24628")
private Integer totalPrice;
@Schema(description = "实际支付金额", example = "11386")
private Integer payPrice;
@Schema(description = "钱包赠送金额", example = "6594")
private Integer bonusPrice;
@Schema(description = "充值套餐编号", example = "2831")
private Long packageId;
@Schema(description = "是否支付", example = "2")
private Boolean payStatus;
@Schema(description = "支付订单编号", example = "5515")
private Long payOrderId;
@Schema(description = "支付成功的支付渠道")
private String payChannelCode;
@Schema(description = "订单支付时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] payTime;
@Schema(description = "支付退款单编号", example = "22733")
private Long payRefundId;
@Schema(description = "退款金额(包含赠送金额)", example = "15904")
private Integer refundTotalPrice;
@Schema(description = "退款支付金额", example = "30626")
private Integer refundPayPrice;
@Schema(description = "退款钱包赠送金额", example = "2421")
private Integer refundBonusPrice;
@Schema(description = "退款时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] refundTime;
@Schema(description = "退款状态", example = "2")
private Integer refundStatus;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,91 @@
package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.recharge;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import com.alibaba.excel.annotation.*;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
@Schema(description = "管理后台 - 钱包充值 Response VO")
@Data
@ExcelIgnoreUnannotated
public class WalletRechargeRespVO {
@Schema(description = "id", requiredMode = Schema.RequiredMode.REQUIRED, example = "13484")
@ExcelProperty("id")
private Long id;
@Schema(description = "钱包编号", example = "3219")
@ExcelProperty("钱包编号")
private Long walletId;
@Schema(description = "充值实际到账", example = "24628")
@ExcelProperty("充值实际到账")
private Integer totalPrice;
@Schema(description = "实际支付金额", example = "11386")
@ExcelProperty("实际支付金额")
private Integer payPrice;
@Schema(description = "钱包赠送金额", example = "6594")
@ExcelProperty("钱包赠送金额")
private Integer bonusPrice;
@Schema(description = "充值套餐编号", example = "2831")
@ExcelProperty("充值套餐编号")
private Long packageId;
@Schema(description = "是否支付", example = "2")
@ExcelProperty(value = "是否支付", converter = DictConvert.class)
@DictFormat("pay_wallet_recharge_pay_status") // TODO 代码优化建议设置到对应的 DictTypeConstants 枚举类中
private Boolean payStatus;
@Schema(description = "支付订单编号", example = "5515")
@ExcelProperty("支付订单编号")
private Long payOrderId;
@Schema(description = "支付成功的支付渠道")
@ExcelProperty("支付成功的支付渠道")
private String payChannelCode;
@Schema(description = "订单支付时间")
@ExcelProperty("订单支付时间")
private LocalDateTime payTime;
@Schema(description = "支付退款单编号", example = "22733")
@ExcelProperty("支付退款单编号")
private Long payRefundId;
@Schema(description = "退款金额(包含赠送金额)", example = "15904")
@ExcelProperty("退款金额(包含赠送金额)")
private Integer refundTotalPrice;
@Schema(description = "退款支付金额", example = "30626")
@ExcelProperty("退款支付金额")
private Integer refundPayPrice;
@Schema(description = "退款钱包赠送金额", example = "2421")
@ExcelProperty("退款钱包赠送金额")
private Integer refundBonusPrice;
@Schema(description = "退款时间")
@ExcelProperty("退款时间")
private LocalDateTime refundTime;
@Schema(description = "退款状态", example = "2")
@ExcelProperty("退款状态")
private Integer refundStatus;
@Schema(description = "创建时间")
@ExcelProperty("创建时间")
private LocalDateTime createTime;
private String avatar;
private String name;
}

View File

@ -0,0 +1,62 @@
package cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.recharge;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
import javax.validation.constraints.*;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 钱包充值新增/修改 Request VO")
@Data
public class WalletRechargeSaveReqVO {
@Schema(description = "id", requiredMode = Schema.RequiredMode.REQUIRED, example = "13484")
private Long id;
@Schema(description = "钱包编号", example = "3219")
private Long walletId;
@Schema(description = "充值实际到账", example = "24628")
private Integer totalPrice;
@Schema(description = "实际支付金额", example = "11386")
private Integer payPrice;
@Schema(description = "钱包赠送金额", example = "6594")
private Integer bonusPrice;
@Schema(description = "充值套餐编号", example = "2831")
private Long packageId;
@Schema(description = "是否支付", example = "2")
private Boolean payStatus;
@Schema(description = "支付订单编号", example = "5515")
private Long payOrderId;
@Schema(description = "支付成功的支付渠道")
private String payChannelCode;
@Schema(description = "订单支付时间")
private LocalDateTime payTime;
@Schema(description = "支付退款单编号", example = "22733")
private Long payRefundId;
@Schema(description = "退款金额(包含赠送金额)", example = "15904")
private Integer refundTotalPrice;
@Schema(description = "退款支付金额", example = "30626")
private Integer refundPayPrice;
@Schema(description = "退款钱包赠送金额", example = "2421")
private Integer refundBonusPrice;
@Schema(description = "退款时间")
private LocalDateTime refundTime;
@Schema(description = "退款状态", example = "2")
private Integer refundStatus;
}

View File

@ -4,10 +4,7 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@ -114,4 +111,10 @@ public class PayWalletRechargeDO extends BaseDO {
*/
private Integer refundStatus;
@TableField(exist = false)
private String avatar;
@TableField(exist = false)
private String name;
}

View File

@ -0,0 +1,95 @@
package cn.iocoder.yudao.module.pay.dal.dataobject.wallet;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 钱包充值 DO
*
* @author 管理员
*/
@TableName("pay_wallet_recharge")
@KeySequence("pay_wallet_recharge_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class WalletRechargeDO extends BaseDO {
/**
* id
*/
@TableId
private Long id;
/**
* 钱包编号
*/
private Long walletId;
/**
* 充值实际到账
*/
private Integer totalPrice;
/**
* 实际支付金额
*/
private Integer payPrice;
/**
* 钱包赠送金额
*/
private Integer bonusPrice;
/**
* 充值套餐编号
*/
private Long packageId;
/**
* 是否支付
*
* 枚举 {@link TODO pay_wallet_recharge_pay_status 对应的类}
*/
private Boolean payStatus;
/**
* 支付订单编号
*/
private Long payOrderId;
/**
* 支付成功的支付渠道
*/
private String payChannelCode;
/**
* 订单支付时间
*/
private LocalDateTime payTime;
/**
* 支付退款单编号
*/
private Long payRefundId;
/**
* 退款金额(包含赠送金额)
*/
private Integer refundTotalPrice;
/**
* 退款支付金额
*/
private Integer refundPayPrice;
/**
* 退款钱包赠送金额
*/
private Integer refundBonusPrice;
/**
* 退款时间
*/
private LocalDateTime refundTime;
/**
* 退款状态
*/
private Integer refundStatus;
}

View File

@ -4,8 +4,10 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.recharge.WalletRechargePageReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.WalletRechargeDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
@ -28,4 +30,25 @@ public interface PayWalletRechargeMapper extends BaseMapperX<PayWalletRechargeDO
.orderByDesc(PayWalletRechargeDO::getId));
}
default PageResult<PayWalletRechargeDO> selectPage(WalletRechargePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<PayWalletRechargeDO>()
.eqIfPresent(PayWalletRechargeDO::getWalletId, reqVO.getWalletId())
.eqIfPresent(PayWalletRechargeDO::getTotalPrice, reqVO.getTotalPrice())
.eqIfPresent(PayWalletRechargeDO::getPayPrice, reqVO.getPayPrice())
.eqIfPresent(PayWalletRechargeDO::getBonusPrice, reqVO.getBonusPrice())
.eqIfPresent(PayWalletRechargeDO::getPackageId, reqVO.getPackageId())
.eqIfPresent(PayWalletRechargeDO::getPayStatus, reqVO.getPayStatus())
.eqIfPresent(PayWalletRechargeDO::getPayOrderId, reqVO.getPayOrderId())
.eqIfPresent(PayWalletRechargeDO::getPayChannelCode, reqVO.getPayChannelCode())
.betweenIfPresent(PayWalletRechargeDO::getPayTime, reqVO.getPayTime())
.eqIfPresent(PayWalletRechargeDO::getPayRefundId, reqVO.getPayRefundId())
.eqIfPresent(PayWalletRechargeDO::getRefundTotalPrice, reqVO.getRefundTotalPrice())
.eqIfPresent(PayWalletRechargeDO::getRefundPayPrice, reqVO.getRefundPayPrice())
.eqIfPresent(PayWalletRechargeDO::getRefundBonusPrice, reqVO.getRefundBonusPrice())
.betweenIfPresent(PayWalletRechargeDO::getRefundTime, reqVO.getRefundTime())
.eqIfPresent(PayWalletRechargeDO::getRefundStatus, reqVO.getRefundStatus())
.betweenIfPresent(PayWalletRechargeDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(PayWalletRechargeDO::getId));
}
}

View File

@ -2,9 +2,11 @@ package cn.iocoder.yudao.module.pay.service.wallet;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.recharge.WalletRechargePageReqVO;
import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.WalletRechargeDO;
/**
* 钱包充值 Service 接口
@ -61,4 +63,12 @@ public interface PayWalletRechargeService {
*/
void updateWalletRechargeRefunded(Long id, Long payRefundId);
/**
* 获得钱包充值分页
*
* @param pageReqVO 分页查询
* @return 钱包充值分页
*/
PageResult<PayWalletRechargeDO> getWalletRechargePage(WalletRechargePageReqVO pageReqVO);
}

View File

@ -4,14 +4,19 @@ import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.recharge.WalletRechargePageReqVO;
import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.WalletRechargeDO;
import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletMapper;
import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletRechargeMapper;
import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
@ -53,9 +58,14 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
private static final String WALLET_RECHARGE_ORDER_SUBJECT = "钱包余额充值";
@Resource
private MemberUserApi memberUserApi;
@Resource
private PayWalletRechargeMapper walletRechargeMapper;
@Resource
private PayWalletMapper payWalletMapper;
@Resource
private PayWalletService payWalletService;
@Resource
private PayOrderService payOrderService;
@ -205,6 +215,24 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
walletRechargeMapper.updateByIdAndRefunded(id, WAITING.getStatus(), updateObj);
}
@Override
public PageResult<PayWalletRechargeDO> getWalletRechargePage(WalletRechargePageReqVO pageReqVO) {
PageResult<PayWalletRechargeDO> payWalletRechargeDOPageResult = walletRechargeMapper.selectPage(pageReqVO);
for (int i = 0; i < payWalletRechargeDOPageResult.getList().size(); i++) {
PayWalletRechargeDO payWalletRechargeDO = payWalletRechargeDOPageResult.getList().get(i);
payWalletRechargeDO.setPayPrice((int) (payWalletRechargeDO.getPayPrice() * 0.01));
//获取用户头像和昵称
PayWalletDO payWalletDO = payWalletMapper.selectById(payWalletRechargeDO.getWalletId());
MemberUserRespDTO user = memberUserApi.getUser(payWalletDO.getUserId());
payWalletRechargeDO.setName(user.getNickname());
payWalletRechargeDO.setAvatar(user.getAvatar());
}
return payWalletRechargeDOPageResult;
}
private PayRefundDO validateWalletRechargeCanRefunded(PayWalletRechargeDO walletRecharge, Long payRefundId) {
// 1. 校验退款订单匹配
if (notEqual(walletRecharge.getPayRefundId(), payRefundId)) {
@ -295,4 +323,5 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
return payOrder;
}
}

View File

@ -27,4 +27,7 @@ public interface NotifyMessageSendApi {
*/
Long sendSingleMessageToMember(@Valid NotifySendSingleToUserReqDTO reqDTO);
}

View File

@ -40,10 +40,12 @@ public class DictDataController {
@Resource
private DictDataService dictDataService;
@Resource
public DictDataApi dictDataApi;
@PostMapping("/create")
@Operation(summary = "新增字典数据")
@PreAuthorize("@ss.hasPermission('system:dict:create')")
@ -159,6 +161,54 @@ public class DictDataController {
}
/**
* 修改装修商品分类字典数据
*/
@GetMapping(value = "/diy-template-goods")
public CommonResult<String> setGoods(String id) {
List<DictDataRespDTO> dictDataList = dictDataApi.getDictDataList("diy-template-goods");
DictDataRespDTO dictDataRespDTO = dictDataList.get(0);
DictDataSaveReqVO dictDataSaveReqVO = BeanUtils.toBean(dictDataRespDTO, DictDataSaveReqVO.class);
dictDataSaveReqVO.setValue(id);
dictDataService.updateDictData(dictDataSaveReqVO);
return success(id);
}
/**
* 修改装修商品分类字典数据
*/
@GetMapping(value = "/getGoods")
public CommonResult<String> getGoods() {
List<DictDataRespDTO> dictDataList = dictDataApi.getDictDataList("diy-template-goods");
DictDataRespDTO dictDataRespDTO = dictDataList.get(0);
return success(dictDataRespDTO.getValue());
}
/**
* 修改装修装修风格字典数据
*/
@GetMapping(value = "/diy-template-theme")
public CommonResult<String> setTheme(String id) {
List<DictDataRespDTO> dictDataList = dictDataApi.getDictDataList("diy-template-theme");
DictDataRespDTO dictDataRespDTO = dictDataList.get(0);
DictDataSaveReqVO dictDataSaveReqVO = BeanUtils.toBean(dictDataRespDTO, DictDataSaveReqVO.class);
dictDataSaveReqVO.setValue(id);
dictDataService.updateDictData(dictDataSaveReqVO);
return success(id);
}
/**
* 修改装修装修风格字典数据
*/
@GetMapping(value = "/getTheme")
public CommonResult<String> getTheme() {
List<DictDataRespDTO> dictDataList = dictDataApi.getDictDataList("diy-template-theme");
DictDataRespDTO dictDataRespDTO = dictDataList.get(0);
return success(dictDataRespDTO.getValue());
}
}

View File

@ -254,6 +254,7 @@ public class SocialClientServiceImpl implements SocialClientService {
ObjUtil.defaultIfNull(reqVO.getAutoColor(), SocialWxQrcodeReqDTO.AUTO_COLOR),
null,
ObjUtil.defaultIfNull(reqVO.getHyaline(), SocialWxQrcodeReqDTO.HYALINE));
} catch (WxErrorException e) {
log.error("[getWxQrcode][reqVO({})) 获得小程序码失败]", reqVO, e);
throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_QRCODE_ERROR);

View File

@ -57,7 +57,7 @@ spring:
# url: jdbc:kingbase8://127.0.0.1:54321/test # 人大金仓 KingbaseES 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss 连接的示例
username: root
# password: 123456
# password: 123456
password: xpower1234
# username: sa # SQL Server 连接的示例
# password: Yudao@2024 # SQL Server 连接的示例
@ -228,7 +228,7 @@ yudao:
enable: false
demo: false # 关闭演示模式
wxa-code:
env-version: develop # 小程序版本: 正式版为 "release";体验版为 "trial";开发版为 "develop"
env-version: release # 小程序版本: 正式版为 "release";体验版为 "trial";开发版为 "develop"
tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
justauth:

View File

@ -210,7 +210,7 @@ yudao:
websocket:
enable: true # websocket的开关
path: /infra/ws # 路径
sender-type: local # 消息发送的类型,可选值为 local、redis、rocketmq、kafka、rabbitmq
sender-type: redis # 消息发送的类型,可选值为 local、redis、rocketmq、kafka、rabbitmq
sender-rocketmq:
topic: ${spring.application.name}-websocket # 消息发送的 RocketMQ Topic
consumer-group: ${spring.application.name}-websocket-consumer # 消息发送的 RocketMQ Consumer Group