Merge remote-tracking branch 'yudao/master' into dhb52_master
This commit is contained in:
commit
e106fb73b6
@ -4,10 +4,10 @@ NODE_ENV=development
|
||||
VITE_DEV=true
|
||||
|
||||
# 请求路径
|
||||
VITE_BASE_URL='http://localhost:48080'
|
||||
VITE_BASE_URL='http://127.0.0.1:48080'
|
||||
|
||||
# 上传路径
|
||||
VITE_UPLOAD_URL='http://localhost:48080/admin-api/infra/file/upload'
|
||||
VITE_UPLOAD_URL='http://127.0.0.1:48080/admin-api/infra/file/upload'
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASEPATH=/dev-api
|
||||
|
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author: zyna
|
||||
* @Date: 2023-11-05 13:34:41
|
||||
* @LastEditTime: 2023-11-11 16:20:19
|
||||
* @LastEditTime: 2023-11-26 20:47:04
|
||||
* @FilePath: \yudao-ui-admin-vue3\src\api\crm\contact\index.ts
|
||||
* @Description:
|
||||
*/
|
||||
@ -22,13 +22,15 @@ export interface ContactVO {
|
||||
id: number
|
||||
parentId: number
|
||||
qq: number
|
||||
webchat: string
|
||||
wechat: string
|
||||
sex: number
|
||||
policyMakers: boolean
|
||||
master: boolean
|
||||
creatorName: string
|
||||
updateTime?: Date
|
||||
createTime?: Date
|
||||
customerName: string
|
||||
customerName: string,
|
||||
areaName: string,
|
||||
ownerUserName: string
|
||||
}
|
||||
|
||||
// 查询crm联系人列表
|
||||
@ -60,6 +62,6 @@ export const deleteContact = async (id: number) => {
|
||||
export const exportContact = async (params) => {
|
||||
return await request.download({ url: `/crm/contact/export-excel`, params })
|
||||
}
|
||||
export const simpleAlllist = async () => {
|
||||
return await request.get({ url: `/crm/contact/simpleAlllist` })
|
||||
export const simpleAllList = async () => {
|
||||
return await request.get({ url: `/crm/contact/simple-all-list` })
|
||||
}
|
||||
|
@ -62,3 +62,7 @@ export const deleteCustomer = async (id: number) => {
|
||||
export const exportCustomer = async (params) => {
|
||||
return await request.download({ url: `/crm/customer/export-excel`, params })
|
||||
}
|
||||
//客户列表
|
||||
export const queryAllList = async () => {
|
||||
return await request.get({ url: `/crm/customer/query-all-list` })
|
||||
}
|
@ -6,9 +6,9 @@ export interface PermissionVO {
|
||||
bizType: number | undefined // Crm 类型
|
||||
bizId: number | undefined // Crm 类型数据编号
|
||||
level: number | undefined // 权限级别
|
||||
deptName?: string // 部门名称 // 岗位名称数组 TODO @puhui999:数组?
|
||||
deptName?: string // 部门名称
|
||||
nickname?: string // 用户昵称
|
||||
postNames?: string // 岗位名称数组 TODO @puhui999:数组?
|
||||
postNames?: string[] // 岗位名称数组
|
||||
createTime?: Date
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ export const getPermissionList = async (params) => {
|
||||
|
||||
// 新增团队成员
|
||||
export const createPermission = async (data: PermissionVO) => {
|
||||
return await request.post({ url: `/crm/permission/add`, data })
|
||||
return await request.post({ url: `/crm/permission/create`, data })
|
||||
}
|
||||
|
||||
// 修改团队成员权限级别
|
||||
|
@ -73,6 +73,13 @@ export function getCouponTemplatePage(params: PageParam) {
|
||||
})
|
||||
}
|
||||
|
||||
// 获得优惠劵模板分页
|
||||
export function getCouponTemplateList(ids: number[]) {
|
||||
return request.get({
|
||||
url: `/promotion/coupon-template/list?ids=${ids}`
|
||||
})
|
||||
}
|
||||
|
||||
// 导出优惠劵模板 Excel
|
||||
export function exportCouponTemplateExcel(params: PageParam) {
|
||||
return request.get({
|
||||
|
@ -26,6 +26,6 @@ export const getUserPage = (query) => {
|
||||
// 同步公众号粉丝
|
||||
export const syncUser = (accountId) => {
|
||||
return request.post({
|
||||
url: '/mp/tag/sync?accountId=' + accountId
|
||||
url: '/mp/user/sync?accountId=' + accountId
|
||||
})
|
||||
}
|
||||
|
@ -35,3 +35,8 @@ export const updateNotice = (data: NoticeVO) => {
|
||||
export const deleteNotice = (id: number) => {
|
||||
return request.delete({ url: '/system/notice/delete?id=' + id })
|
||||
}
|
||||
|
||||
// 推送公告
|
||||
export const pushNotice = (id: number) => {
|
||||
return request.post({ url: '/system/notice/push?id=' + id })
|
||||
}
|
||||
|
@ -0,0 +1,78 @@
|
||||
import * as CouponTemplateApi from '@/api/mall/promotion/coupon/couponTemplate'
|
||||
import { CouponTemplateValidityTypeEnum, PromotionDiscountTypeEnum } from '@/utils/constants'
|
||||
import { floatToFixed2 } from '@/utils'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
|
||||
// 优惠值
|
||||
export const CouponDiscount = defineComponent({
|
||||
name: 'CouponDiscount',
|
||||
props: {
|
||||
coupon: {
|
||||
type: CouponTemplateApi.CouponTemplateVO
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const coupon = props.coupon as CouponTemplateApi.CouponTemplateVO
|
||||
// 折扣
|
||||
let value = coupon.discountPercent + ''
|
||||
let suffix = ' 折'
|
||||
// 满减
|
||||
if (coupon.discountType === PromotionDiscountTypeEnum.PRICE.type) {
|
||||
value = floatToFixed2(coupon.discountPrice)
|
||||
suffix = ' 元'
|
||||
}
|
||||
return () => (
|
||||
<div>
|
||||
<span class={'text-20px font-bold'}>{value}</span>
|
||||
<span>{suffix}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// 优惠描述
|
||||
export const CouponDiscountDesc = defineComponent({
|
||||
name: 'CouponDiscountDesc',
|
||||
props: {
|
||||
coupon: {
|
||||
type: CouponTemplateApi.CouponTemplateVO
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const coupon = props.coupon as CouponTemplateApi.CouponTemplateVO
|
||||
// 使用条件
|
||||
const useCondition = coupon.usePrice > 0 ? `满${floatToFixed2(coupon.usePrice)}元,` : ''
|
||||
// 优惠描述
|
||||
const discountDesc =
|
||||
coupon.discountType === PromotionDiscountTypeEnum.PRICE.type
|
||||
? `减${floatToFixed2(coupon.discountPrice)}元`
|
||||
: `打${coupon.discountPercent}折`
|
||||
return () => (
|
||||
<div>
|
||||
<span>{useCondition}</span>
|
||||
<span>{discountDesc}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// 有效期
|
||||
export const CouponValidTerm = defineComponent({
|
||||
name: 'CouponValidTerm',
|
||||
props: {
|
||||
coupon: {
|
||||
type: CouponTemplateApi.CouponTemplateVO
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const coupon = props.coupon as CouponTemplateApi.CouponTemplateVO
|
||||
const text =
|
||||
coupon.validityType === CouponTemplateValidityTypeEnum.DATE.type
|
||||
? `有效期:${formatDate(coupon.validStartTime, 'YYYY-MM-DD')} 至 ${formatDate(
|
||||
coupon.validEndTime,
|
||||
'YYYY-MM-DD'
|
||||
)}`
|
||||
: `领取后第 ${coupon.fixedStartTerm} - ${coupon.fixedEndTerm} 天内可用`
|
||||
return () => <div>{text}</div>
|
||||
}
|
||||
})
|
@ -0,0 +1,47 @@
|
||||
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||
|
||||
/** 商品卡片属性 */
|
||||
export interface CouponCardProperty {
|
||||
// 列数
|
||||
columns: number
|
||||
// 背景图
|
||||
bgImg: string
|
||||
// 文字颜色
|
||||
textColor: string
|
||||
// 按钮样式
|
||||
button: {
|
||||
// 颜色
|
||||
color: string
|
||||
// 背景颜色
|
||||
bgColor: string
|
||||
}
|
||||
// 间距
|
||||
space: number
|
||||
// 优惠券编号列表
|
||||
couponIds: number[]
|
||||
// 组件样式
|
||||
style: ComponentStyle
|
||||
}
|
||||
|
||||
// 定义组件
|
||||
export const component = {
|
||||
id: 'CouponCard',
|
||||
name: '优惠券',
|
||||
icon: 'ep:ticket',
|
||||
property: {
|
||||
columns: 1,
|
||||
bgImg: '',
|
||||
textColor: '#E9B461',
|
||||
button: {
|
||||
color: '#434343',
|
||||
bgColor: ''
|
||||
},
|
||||
space: 0,
|
||||
couponIds: [],
|
||||
style: {
|
||||
bgType: 'color',
|
||||
bgColor: '',
|
||||
marginBottom: 8
|
||||
} as ComponentStyle
|
||||
}
|
||||
} as DiyComponent<CouponCardProperty>
|
142
src/components/DiyEditor/components/mobile/CouponCard/index.vue
Normal file
142
src/components/DiyEditor/components/mobile/CouponCard/index.vue
Normal file
@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<el-scrollbar class="z-1 min-h-30px" wrap-class="w-full" ref="containerRef">
|
||||
<div
|
||||
class="flex flex-row text-12px"
|
||||
:style="{
|
||||
gap: `${property.space}px`,
|
||||
width: scrollbarWidth
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="box-content"
|
||||
:style="{
|
||||
background: property.bgImg
|
||||
? `url(${property.bgImg}) 100% center / 100% 100% no-repeat`
|
||||
: '#fff',
|
||||
width: `${couponWidth}px`,
|
||||
color: property.textColor
|
||||
}"
|
||||
v-for="(coupon, index) in couponList"
|
||||
:key="index"
|
||||
>
|
||||
<!-- 布局1:1列-->
|
||||
<div v-if="property.columns === 1" class="m-l-16px flex flex-row justify-between p-8px">
|
||||
<div class="flex flex-col justify-evenly gap-4px">
|
||||
<!-- 优惠值 -->
|
||||
<CouponDiscount :coupon="coupon" />
|
||||
<!-- 优惠描述 -->
|
||||
<CouponDiscountDesc :coupon="coupon" />
|
||||
<!-- 有效期 -->
|
||||
<CouponValidTerm :coupon="coupon" />
|
||||
</div>
|
||||
<div class="flex flex-col justify-evenly">
|
||||
<div
|
||||
class="rounded-20px p-x-8px p-y-2px"
|
||||
:style="{
|
||||
color: property.button.color,
|
||||
background: property.button.bgColor
|
||||
}"
|
||||
>
|
||||
立即领取
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 布局2:2列-->
|
||||
<div
|
||||
v-else-if="property.columns === 2"
|
||||
class="m-l-16px flex flex-row justify-between p-8px"
|
||||
>
|
||||
<div class="flex flex-col justify-evenly gap-4px">
|
||||
<!-- 优惠值 -->
|
||||
<CouponDiscount :coupon="coupon" />
|
||||
<div>{{ coupon.name }}</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
class="h-full w-20px rounded-20px p-x-2px p-y-8px text-center"
|
||||
:style="{
|
||||
color: property.button.color,
|
||||
background: property.button.bgColor
|
||||
}"
|
||||
>
|
||||
立即领取
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 布局3:3列-->
|
||||
<div v-else class="flex flex-col items-center justify-around gap-4px p-4px">
|
||||
<!-- 优惠值 -->
|
||||
<CouponDiscount :coupon="coupon" />
|
||||
<div>{{ coupon.name }}</div>
|
||||
<div
|
||||
class="rounded-20px p-x-8px p-y-2px"
|
||||
:style="{
|
||||
color: property.button.color,
|
||||
background: property.button.bgColor
|
||||
}"
|
||||
>
|
||||
立即领取
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { CouponCardProperty } from './config'
|
||||
import * as CouponTemplateApi from '@/api/mall/promotion/coupon/couponTemplate'
|
||||
import { CouponDiscount } from './component'
|
||||
import {
|
||||
CouponDiscountDesc,
|
||||
CouponValidTerm
|
||||
} from '@/components/DiyEditor/components/mobile/CouponCard/component'
|
||||
|
||||
/** 商品卡片 */
|
||||
defineOptions({ name: 'CouponCard' })
|
||||
// 定义属性
|
||||
const props = defineProps<{ property: CouponCardProperty }>()
|
||||
// 商品列表
|
||||
const couponList = ref<CouponTemplateApi.CouponTemplateVO[]>([])
|
||||
watch(
|
||||
() => props.property.couponIds,
|
||||
async () => {
|
||||
if (props.property.couponIds?.length > 0) {
|
||||
couponList.value = await CouponTemplateApi.getCouponTemplateList(props.property.couponIds)
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
// 手机宽度
|
||||
const phoneWidth = ref(375)
|
||||
// 容器
|
||||
const containerRef = ref()
|
||||
// 滚动条宽度
|
||||
const scrollbarWidth = ref('100%')
|
||||
// 优惠券的宽度
|
||||
const couponWidth = ref(375)
|
||||
// 计算布局参数
|
||||
watch(
|
||||
() => [props.property, phoneWidth, couponList.value.length],
|
||||
() => {
|
||||
// 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
|
||||
couponWidth.value =
|
||||
(phoneWidth.value * 0.95 - props.property.space * (props.property.columns - 1)) /
|
||||
props.property.columns
|
||||
// 显示滚动条
|
||||
scrollbarWidth.value = `${
|
||||
couponWidth.value * couponList.value.length +
|
||||
props.property.space * (couponList.value.length - 1)
|
||||
}px`
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
onMounted(() => {
|
||||
// 提取手机宽度
|
||||
phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 375
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<ComponentContainerProperty v-model="formData.style">
|
||||
<el-form label-width="80px" :model="formData">
|
||||
<el-card header="优惠券列表" class="property-group" shadow="never">
|
||||
<div
|
||||
v-for="(coupon, index) in couponList"
|
||||
:key="index"
|
||||
class="flex items-center justify-between"
|
||||
>
|
||||
<el-text size="large" truncated>{{ coupon.name }}</el-text>
|
||||
<el-text type="info" truncated>
|
||||
<span v-if="coupon.usePrice > 0">满{{ floatToFixed2(coupon.usePrice) }}元,</span>
|
||||
<span v-if="coupon.discountType === PromotionDiscountTypeEnum.PRICE.type">
|
||||
减{{ floatToFixed2(coupon.discountPrice) }}元
|
||||
</span>
|
||||
<span v-else> 打{{ coupon.discountPercent }}折 </span>
|
||||
</el-text>
|
||||
</div>
|
||||
<el-form-item label-width="0">
|
||||
<el-button @click="handleAddCoupon" type="primary" plain class="m-t-8px w-full">
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 添加
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card header="优惠券样式" class="property-group" shadow="never">
|
||||
<el-form-item label="列数" prop="type">
|
||||
<el-radio-group v-model="formData.columns">
|
||||
<el-tooltip class="item" content="一列" placement="bottom">
|
||||
<el-radio-button :label="1">
|
||||
<Icon icon="fluent:text-column-one-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" content="二列" placement="bottom">
|
||||
<el-radio-button :label="2">
|
||||
<Icon icon="fluent:text-column-two-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" content="三列" placement="bottom">
|
||||
<el-radio-button :label="3">
|
||||
<Icon icon="fluent:text-column-three-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="背景图片" prop="bgImg">
|
||||
<UploadImg v-model="formData.bgImg" height="80px" width="100%" class="min-w-160px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="文字颜色" prop="textColor">
|
||||
<ColorInput v-model="formData.textColor" />
|
||||
</el-form-item>
|
||||
<el-form-item label="按钮背景" prop="button.bgColor">
|
||||
<ColorInput v-model="formData.button.bgColor" />
|
||||
</el-form-item>
|
||||
<el-form-item label="按钮文字" prop="button.color">
|
||||
<ColorInput v-model="formData.button.color" />
|
||||
</el-form-item>
|
||||
<el-form-item label="间隔" prop="space">
|
||||
<el-slider
|
||||
v-model="formData.space"
|
||||
:max="100"
|
||||
:min="0"
|
||||
show-input
|
||||
input-size="small"
|
||||
:show-input-controls="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</ComponentContainerProperty>
|
||||
<!-- 优惠券选择 -->
|
||||
<CouponSelect ref="couponSelectDialog" v-model:multiple-selection="couponList" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { CouponCardProperty } from './config'
|
||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||
import * as CouponTemplateApi from '@/api/mall/promotion/coupon/couponTemplate'
|
||||
import { floatToFixed2 } from '@/utils'
|
||||
import { PromotionDiscountTypeEnum } from '@/utils/constants'
|
||||
import CouponSelect from '@/views/mall/promotion/coupon/components/CouponSelect.vue'
|
||||
|
||||
// 优惠券卡片属性面板
|
||||
defineOptions({ name: 'CouponCardProperty' })
|
||||
|
||||
const props = defineProps<{ modelValue: CouponCardProperty }>()
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||
|
||||
// 优惠券列表
|
||||
const couponList = ref<CouponTemplateApi.CouponTemplateVO[]>([])
|
||||
const couponSelectDialog = ref()
|
||||
// 添加优惠券
|
||||
const handleAddCoupon = () => {
|
||||
couponSelectDialog.value.open()
|
||||
}
|
||||
watch(
|
||||
() => couponList.value,
|
||||
() => {
|
||||
formData.value.couponIds = couponList.value.map((coupon) => coupon.id)
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
@ -62,7 +62,7 @@ export interface ProductCardFieldProperty {
|
||||
export const component = {
|
||||
id: 'ProductCard',
|
||||
name: '商品卡片',
|
||||
icon: 'system-uicons:carousel',
|
||||
icon: 'fluent:text-column-two-left-24-filled',
|
||||
property: {
|
||||
layoutType: 'oneColBigImg',
|
||||
fields: {
|
||||
|
@ -0,0 +1,64 @@
|
||||
import { ComponentStyle, DiyComponent } from '@/components/DiyEditor/util'
|
||||
|
||||
/** 商品卡片属性 */
|
||||
export interface ProductListProperty {
|
||||
// 布局类型:双列 | 三列 | 水平滑动
|
||||
layoutType: 'twoCol' | 'threeCol' | 'horizSwiper'
|
||||
// 商品字段
|
||||
fields: {
|
||||
// 商品名称
|
||||
name: ProductListFieldProperty
|
||||
// 商品价格
|
||||
price: ProductListFieldProperty
|
||||
}
|
||||
// 角标
|
||||
badge: {
|
||||
// 是否显示
|
||||
show: boolean
|
||||
// 角标图片
|
||||
imgUrl: string
|
||||
}
|
||||
// 上圆角
|
||||
borderRadiusTop: number
|
||||
// 下圆角
|
||||
borderRadiusBottom: number
|
||||
// 间距
|
||||
space: number
|
||||
// 商品编号列表
|
||||
spuIds: number[]
|
||||
// 组件样式
|
||||
style: ComponentStyle
|
||||
}
|
||||
// 商品字段
|
||||
export interface ProductListFieldProperty {
|
||||
// 是否显示
|
||||
show: boolean
|
||||
// 颜色
|
||||
color: string
|
||||
}
|
||||
|
||||
// 定义组件
|
||||
export const component = {
|
||||
id: 'ProductList',
|
||||
name: '商品栏',
|
||||
icon: 'fluent:text-column-two-24-filled',
|
||||
property: {
|
||||
layoutType: 'twoCol',
|
||||
fields: {
|
||||
name: { show: true, color: '#000' },
|
||||
price: { show: true, color: '#ff3000' }
|
||||
},
|
||||
badge: { show: false, imgUrl: '' },
|
||||
borderRadiusTop: 8,
|
||||
borderRadiusBottom: 8,
|
||||
space: 8,
|
||||
spuIds: [],
|
||||
style: {
|
||||
bgType: 'color',
|
||||
bgColor: '',
|
||||
marginLeft: 8,
|
||||
marginRight: 8,
|
||||
marginBottom: 8
|
||||
} as ComponentStyle
|
||||
}
|
||||
} as DiyComponent<ProductListProperty>
|
131
src/components/DiyEditor/components/mobile/ProductList/index.vue
Normal file
131
src/components/DiyEditor/components/mobile/ProductList/index.vue
Normal file
@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<el-scrollbar class="z-1 min-h-30px" wrap-class="w-full" ref="containerRef">
|
||||
<!-- 商品网格 -->
|
||||
<div
|
||||
class="grid overflow-x-auto"
|
||||
:style="{
|
||||
gridGap: `${property.space}px`,
|
||||
gridTemplateColumns,
|
||||
width: scrollbarWidth
|
||||
}"
|
||||
>
|
||||
<!-- 商品 -->
|
||||
<div
|
||||
class="relative box-content flex flex-row flex-wrap overflow-hidden bg-white"
|
||||
:style="{
|
||||
borderTopLeftRadius: `${property.borderRadiusTop}px`,
|
||||
borderTopRightRadius: `${property.borderRadiusTop}px`,
|
||||
borderBottomLeftRadius: `${property.borderRadiusBottom}px`,
|
||||
borderBottomRightRadius: `${property.borderRadiusBottom}px`
|
||||
}"
|
||||
v-for="(spu, index) in spuList"
|
||||
:key="index"
|
||||
>
|
||||
<!-- 角标 -->
|
||||
<div
|
||||
v-if="property.badge.show"
|
||||
class="absolute left-0 top-0 z-1 items-center justify-center"
|
||||
>
|
||||
<el-image fit="cover" :src="property.badge.imgUrl" class="h-26px w-38px" />
|
||||
</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
|
||||
v-if="property.fields.name.show"
|
||||
class="truncate text-12px"
|
||||
:style="{ color: property.fields.name.color }"
|
||||
>
|
||||
{{ spu.name }}
|
||||
</div>
|
||||
<div>
|
||||
<!-- 商品价格 -->
|
||||
<span
|
||||
v-if="property.fields.price.show"
|
||||
class="text-12px"
|
||||
:style="{ color: property.fields.price.color }"
|
||||
>
|
||||
¥{{ spu.price }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ProductListProperty } from './config'
|
||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||
|
||||
/** 商品卡片 */
|
||||
defineOptions({ name: 'ProductList' })
|
||||
// 定义属性
|
||||
const props = defineProps<{ property: ProductListProperty }>()
|
||||
// 商品列表
|
||||
const spuList = ref<ProductSpuApi.Spu[]>([])
|
||||
watch(
|
||||
() => props.property.spuIds,
|
||||
async () => {
|
||||
spuList.value = await ProductSpuApi.getSpuDetailList(props.property.spuIds)
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
// 手机宽度
|
||||
const phoneWidth = ref(375)
|
||||
// 容器
|
||||
const containerRef = ref()
|
||||
// 商品的列数
|
||||
const columns = ref(2)
|
||||
// 滚动条宽度
|
||||
const scrollbarWidth = ref('100%')
|
||||
// 商品图大小
|
||||
const imageSize = ref('0')
|
||||
// 商品网络列数
|
||||
const gridTemplateColumns = ref('')
|
||||
// 计算布局参数
|
||||
watch(
|
||||
() => [props.property, phoneWidth, spuList.value.length],
|
||||
() => {
|
||||
// 计算列数
|
||||
columns.value = props.property.layoutType === 'twoCol' ? 2 : 3
|
||||
// 每列的宽度为:(总宽度 - 间距 * (列数 - 1))/ 列数
|
||||
const productWidth =
|
||||
(phoneWidth.value - props.property.space * (columns.value - 1)) / columns.value
|
||||
// 商品图布局:2列时,左右布局 3列时,上下布局
|
||||
imageSize.value = columns.value === 2 ? '64px' : `${productWidth}px`
|
||||
// 根据布局类型,计算行数、列数
|
||||
if (props.property.layoutType === 'horizSwiper') {
|
||||
// 单行显示
|
||||
gridTemplateColumns.value = `repeat(auto-fill, ${productWidth}px)`
|
||||
// 显示滚动条
|
||||
scrollbarWidth.value = `${
|
||||
productWidth * spuList.value.length + props.property.space * (spuList.value.length - 1)
|
||||
}px`
|
||||
} else {
|
||||
// 指定列数
|
||||
gridTemplateColumns.value = `repeat(${columns.value}, auto)`
|
||||
// 不滚动
|
||||
scrollbarWidth.value = '100%'
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
onMounted(() => {
|
||||
// 提取手机宽度
|
||||
phoneWidth.value = containerRef.value?.wrapRef?.offsetWidth || 375
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<ComponentContainerProperty v-model="formData.style">
|
||||
<el-form label-width="80px" :model="formData">
|
||||
<el-card header="商品列表" class="property-group" shadow="never">
|
||||
<SpuShowcase v-model="formData.spuIds" />
|
||||
</el-card>
|
||||
<el-card header="商品样式" class="property-group" shadow="never">
|
||||
<el-form-item label="布局" prop="type">
|
||||
<el-radio-group v-model="formData.layoutType">
|
||||
<el-tooltip class="item" content="双列" placement="bottom">
|
||||
<el-radio-button label="twoCol">
|
||||
<Icon icon="fluent:text-column-two-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" content="三列" placement="bottom">
|
||||
<el-radio-button label="threeCol">
|
||||
<Icon icon="fluent:text-column-three-24-filled" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip class="item" content="水平滑动" placement="bottom">
|
||||
<el-radio-button label="horizSwiper">
|
||||
<Icon icon="system-uicons:carousel" />
|
||||
</el-radio-button>
|
||||
</el-tooltip>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品名称" prop="fields.name.show">
|
||||
<div class="flex gap-8px">
|
||||
<ColorInput v-model="formData.fields.name.color" />
|
||||
<el-checkbox v-model="formData.fields.name.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品价格" prop="fields.price.show">
|
||||
<div class="flex gap-8px">
|
||||
<ColorInput v-model="formData.fields.price.color" />
|
||||
<el-checkbox v-model="formData.fields.price.show" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card header="角标" class="property-group" shadow="never">
|
||||
<el-form-item label="角标" prop="badge.show">
|
||||
<el-switch v-model="formData.badge.show" />
|
||||
</el-form-item>
|
||||
<el-form-item label="角标" prop="badge.imgUrl" v-if="formData.badge.show">
|
||||
<UploadImg v-model="formData.badge.imgUrl" height="44px" width="72px">
|
||||
<template #tip> 建议尺寸:36 * 22 </template>
|
||||
</UploadImg>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
<el-card header="商品样式" class="property-group" shadow="never">
|
||||
<el-form-item label="上圆角" prop="borderRadiusTop">
|
||||
<el-slider
|
||||
v-model="formData.borderRadiusTop"
|
||||
:max="100"
|
||||
:min="0"
|
||||
show-input
|
||||
input-size="small"
|
||||
:show-input-controls="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="下圆角" prop="borderRadiusBottom">
|
||||
<el-slider
|
||||
v-model="formData.borderRadiusBottom"
|
||||
:max="100"
|
||||
:min="0"
|
||||
show-input
|
||||
input-size="small"
|
||||
:show-input-controls="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="间隔" prop="space">
|
||||
<el-slider
|
||||
v-model="formData.space"
|
||||
:max="100"
|
||||
:min="0"
|
||||
show-input
|
||||
input-size="small"
|
||||
:show-input-controls="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
</el-form>
|
||||
</ComponentContainerProperty>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ProductListProperty } from './config'
|
||||
import { usePropertyForm } from '@/components/DiyEditor/util'
|
||||
import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue'
|
||||
|
||||
// 商品卡片属性面板
|
||||
defineOptions({ name: 'ProductListProperty' })
|
||||
|
||||
const props = defineProps<{ modelValue: ProductListProperty }>()
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const { formData } = usePropertyForm(props.modelValue, emit)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
@ -107,11 +107,15 @@ export const PAGE_LIBS = [
|
||||
extended: true,
|
||||
components: ['ImageBar', 'Carousel', 'TitleBar', 'VideoPlayer', 'Divider', 'MagicCube']
|
||||
},
|
||||
{ name: '商品组件', extended: true, components: ['ProductCard'] },
|
||||
{ name: '商品组件', extended: true, components: ['ProductCard', 'ProductList'] },
|
||||
{
|
||||
name: '会员组件',
|
||||
extended: true,
|
||||
components: ['UserCard', 'OrderCard', 'WalletCard', 'CouponCard']
|
||||
components: ['UserCard', 'UserOrder', 'UserWallet', 'UserCoupon']
|
||||
},
|
||||
{ name: '营销组件', extended: true, components: ['Combination', 'Seckill', 'Point', 'Coupon'] }
|
||||
{
|
||||
name: '营销组件',
|
||||
extended: true,
|
||||
components: ['CombinationCard', 'SeckillCard', 'PointCard', 'CouponCard']
|
||||
}
|
||||
] as DiyComponentLibrary[]
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ElDialog v-model="showSearch" :show-close="false" title="菜单搜索">
|
||||
<ElDialog v-if="isModal" v-model="showSearch" :show-close="false" title="菜单搜索">
|
||||
<el-select
|
||||
filterable
|
||||
:reserve-keyword="false"
|
||||
@ -17,11 +17,34 @@
|
||||
/>
|
||||
</el-select>
|
||||
</ElDialog>
|
||||
<div v-else class="custom-hover" @click.stop="showTopSearch = !showTopSearch">
|
||||
<Icon icon="ep:search" />
|
||||
<el-select
|
||||
filterable
|
||||
:reserve-keyword="false"
|
||||
remote
|
||||
placeholder="请输入菜单内容"
|
||||
:remote-method="remoteMethod"
|
||||
class="overflow-hidden transition-all-600"
|
||||
:class="showTopSearch ? 'w-220px ml2' : 'w-0'"
|
||||
@change="handleChange"
|
||||
>
|
||||
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
isModal: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
const router = useRouter() // 路由对象
|
||||
const showSearch = ref(false) // 是否显示弹框
|
||||
const showTopSearch = ref(false) // 是否显示顶部搜索框
|
||||
const value: Ref = ref('') // 用户输入的值
|
||||
|
||||
const routers = router.getRoutes() // 路由对象
|
||||
@ -50,14 +73,21 @@ function remoteMethod(data) {
|
||||
|
||||
function handleChange(path) {
|
||||
router.push({ path })
|
||||
hiddenTopSearch();
|
||||
}
|
||||
|
||||
function hiddenTopSearch() {
|
||||
showTopSearch.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', listenKey)
|
||||
window.addEventListener('click', hiddenTopSearch)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('keydown', listenKey)
|
||||
window.removeEventListener('click', hiddenTopSearch)
|
||||
})
|
||||
|
||||
// 监听 ctrl + k
|
||||
|
@ -74,11 +74,6 @@ const emits = defineEmits<{
|
||||
}>()
|
||||
/** 触发时间范围选中事件 */
|
||||
const emitDateRangePicker = async () => {
|
||||
// 开始与截止在同一天的, 折线图出不来, 需要延长一天
|
||||
if (DateUtil.isSameDay(times.value[0], times.value[1])) {
|
||||
// 前天
|
||||
times.value[0] = DateUtil.formatDate(dayjs(times.value[0]).subtract(1, 'd'))
|
||||
}
|
||||
emits('change', times.value)
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import { Screenfull } from '@/layout/components/Screenfull'
|
||||
import { Breadcrumb } from '@/layout/components/Breadcrumb'
|
||||
import { SizeDropdown } from '@/layout/components/SizeDropdown'
|
||||
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||
import RouterSearch from '@/components/RouterSearch/index.vue'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
|
||||
@ -25,6 +26,9 @@ const hamburger = computed(() => appStore.getHamburger)
|
||||
// 全屏图标
|
||||
const screenfull = computed(() => appStore.getScreenfull)
|
||||
|
||||
// 搜索图片
|
||||
const search = computed(() => appStore.search)
|
||||
|
||||
// 尺寸图标
|
||||
const size = computed(() => appStore.getSize)
|
||||
|
||||
@ -61,6 +65,7 @@ export default defineComponent({
|
||||
{screenfull.value ? (
|
||||
<Screenfull class="custom-hover" color="var(--top-header-text-color)"></Screenfull>
|
||||
) : undefined}
|
||||
{search.value ? (<RouterSearch isModal={false} />) : undefined}
|
||||
{size.value ? (
|
||||
<SizeDropdown class="custom-hover" color="var(--top-header-text-color)"></SizeDropdown>
|
||||
) : undefined}
|
||||
|
@ -437,5 +437,6 @@ export default {
|
||||
btn_zoom_in: '放大',
|
||||
btn_zoom_out: '缩小',
|
||||
preview: '预览'
|
||||
}
|
||||
},
|
||||
'OAuth 2.0': 'OAuth 2.0' // 避免菜单名是 OAuth 2.0 时,一直 warn 报错
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ interface AppState {
|
||||
uniqueOpened: boolean
|
||||
hamburger: boolean
|
||||
screenfull: boolean
|
||||
search: boolean
|
||||
size: boolean
|
||||
locale: boolean
|
||||
message: boolean
|
||||
@ -52,6 +53,7 @@ export const useAppStore = defineStore('app', {
|
||||
uniqueOpened: true, // 是否只保持一个子菜单的展开
|
||||
hamburger: true, // 折叠图标
|
||||
screenfull: true, // 全屏图标
|
||||
search: true, // 搜索图标
|
||||
size: true, // 尺寸图标
|
||||
locale: true, // 多语言图标
|
||||
message: true, // 消息图标
|
||||
|
@ -1,8 +1,8 @@
|
||||
/**
|
||||
* 数据字典工具类
|
||||
*/
|
||||
import { useDictStoreWithOut } from '@/store/modules/dict'
|
||||
import { ElementPlusInfoType } from '@/types/elementPlus'
|
||||
import {useDictStoreWithOut} from '@/store/modules/dict'
|
||||
import {ElementPlusInfoType} from '@/types/elementPlus'
|
||||
|
||||
const dictStore = useDictStoreWithOut()
|
||||
|
||||
@ -13,51 +13,51 @@ const dictStore = useDictStoreWithOut()
|
||||
* @returns {*|Array} 数据字典数组
|
||||
*/
|
||||
export interface DictDataType {
|
||||
dictType: string
|
||||
label: string
|
||||
value: string | number | boolean
|
||||
colorType: ElementPlusInfoType | ''
|
||||
cssClass: string
|
||||
dictType: string
|
||||
label: string
|
||||
value: string | number | boolean
|
||||
colorType: ElementPlusInfoType | ''
|
||||
cssClass: string
|
||||
}
|
||||
|
||||
export const getDictOptions = (dictType: string) => {
|
||||
return dictStore.getDictByType(dictType) || []
|
||||
return dictStore.getDictByType(dictType) || []
|
||||
}
|
||||
|
||||
export const getIntDictOptions = (dictType: string): DictDataType[] => {
|
||||
const dictOption: DictDataType[] = []
|
||||
const dictOptions: DictDataType[] = getDictOptions(dictType)
|
||||
dictOptions.forEach((dict: DictDataType) => {
|
||||
dictOption.push({
|
||||
...dict,
|
||||
value: parseInt(dict.value + '')
|
||||
const dictOption: DictDataType[] = []
|
||||
const dictOptions: DictDataType[] = getDictOptions(dictType)
|
||||
dictOptions.forEach((dict: DictDataType) => {
|
||||
dictOption.push({
|
||||
...dict,
|
||||
value: parseInt(dict.value + '')
|
||||
})
|
||||
})
|
||||
})
|
||||
return dictOption
|
||||
return dictOption
|
||||
}
|
||||
|
||||
export const getStrDictOptions = (dictType: string) => {
|
||||
const dictOption: DictDataType[] = []
|
||||
const dictOptions: DictDataType[] = getDictOptions(dictType)
|
||||
dictOptions.forEach((dict: DictDataType) => {
|
||||
dictOption.push({
|
||||
...dict,
|
||||
value: dict.value + ''
|
||||
const dictOption: DictDataType[] = []
|
||||
const dictOptions: DictDataType[] = getDictOptions(dictType)
|
||||
dictOptions.forEach((dict: DictDataType) => {
|
||||
dictOption.push({
|
||||
...dict,
|
||||
value: dict.value + ''
|
||||
})
|
||||
})
|
||||
})
|
||||
return dictOption
|
||||
return dictOption
|
||||
}
|
||||
|
||||
export const getBoolDictOptions = (dictType: string) => {
|
||||
const dictOption: DictDataType[] = []
|
||||
const dictOptions: DictDataType[] = getDictOptions(dictType)
|
||||
dictOptions.forEach((dict: DictDataType) => {
|
||||
dictOption.push({
|
||||
...dict,
|
||||
value: dict.value + '' === 'true'
|
||||
const dictOption: DictDataType[] = []
|
||||
const dictOptions: DictDataType[] = getDictOptions(dictType)
|
||||
dictOptions.forEach((dict: DictDataType) => {
|
||||
dictOption.push({
|
||||
...dict,
|
||||
value: dict.value + '' === 'true'
|
||||
})
|
||||
})
|
||||
})
|
||||
return dictOption
|
||||
return dictOption
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,12 +67,12 @@ export const getBoolDictOptions = (dictType: string) => {
|
||||
* @return DictDataType 字典对象
|
||||
*/
|
||||
export const getDictObj = (dictType: string, value: any): DictDataType | undefined => {
|
||||
const dictOptions: DictDataType[] = getDictOptions(dictType)
|
||||
for (const dict of dictOptions) {
|
||||
if (dict.value === value + '') {
|
||||
return dict
|
||||
const dictOptions: DictDataType[] = getDictOptions(dictType)
|
||||
for (const dict of dictOptions) {
|
||||
if (dict.value === value + '') {
|
||||
return dict
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,117 +83,121 @@ export const getDictObj = (dictType: string, value: any): DictDataType | undefin
|
||||
* @return 字典名称
|
||||
*/
|
||||
export const getDictLabel = (dictType: string, value: any): string => {
|
||||
const dictOptions: DictDataType[] = getDictOptions(dictType)
|
||||
const dictLabel = ref('')
|
||||
dictOptions.forEach((dict: DictDataType) => {
|
||||
if (dict.value === value + '') {
|
||||
dictLabel.value = dict.label
|
||||
}
|
||||
})
|
||||
return dictLabel.value
|
||||
const dictOptions: DictDataType[] = getDictOptions(dictType)
|
||||
const dictLabel = ref('')
|
||||
dictOptions.forEach((dict: DictDataType) => {
|
||||
if (dict.value === value + '') {
|
||||
dictLabel.value = dict.label
|
||||
}
|
||||
})
|
||||
return dictLabel.value
|
||||
}
|
||||
|
||||
export enum DICT_TYPE {
|
||||
USER_TYPE = 'user_type',
|
||||
COMMON_STATUS = 'common_status',
|
||||
SYSTEM_TENANT_PACKAGE_ID = 'system_tenant_package_id',
|
||||
TERMINAL = 'terminal', // 终端
|
||||
USER_TYPE = 'user_type',
|
||||
COMMON_STATUS = 'common_status',
|
||||
SYSTEM_TENANT_PACKAGE_ID = 'system_tenant_package_id',
|
||||
TERMINAL = 'terminal', // 终端
|
||||
|
||||
// ========== SYSTEM 模块 ==========
|
||||
SYSTEM_USER_SEX = 'system_user_sex',
|
||||
SYSTEM_MENU_TYPE = 'system_menu_type',
|
||||
SYSTEM_ROLE_TYPE = 'system_role_type',
|
||||
SYSTEM_DATA_SCOPE = 'system_data_scope',
|
||||
SYSTEM_NOTICE_TYPE = 'system_notice_type',
|
||||
SYSTEM_OPERATE_TYPE = 'system_operate_type',
|
||||
SYSTEM_LOGIN_TYPE = 'system_login_type',
|
||||
SYSTEM_LOGIN_RESULT = 'system_login_result',
|
||||
SYSTEM_SMS_CHANNEL_CODE = 'system_sms_channel_code',
|
||||
SYSTEM_SMS_TEMPLATE_TYPE = 'system_sms_template_type',
|
||||
SYSTEM_SMS_SEND_STATUS = 'system_sms_send_status',
|
||||
SYSTEM_SMS_RECEIVE_STATUS = 'system_sms_receive_status',
|
||||
SYSTEM_ERROR_CODE_TYPE = 'system_error_code_type',
|
||||
SYSTEM_OAUTH2_GRANT_TYPE = 'system_oauth2_grant_type',
|
||||
SYSTEM_MAIL_SEND_STATUS = 'system_mail_send_status',
|
||||
SYSTEM_NOTIFY_TEMPLATE_TYPE = 'system_notify_template_type',
|
||||
SYSTEM_SOCIAL_TYPE = 'system_social_type',
|
||||
// ========== SYSTEM 模块 ==========
|
||||
SYSTEM_USER_SEX = 'system_user_sex',
|
||||
SYSTEM_MENU_TYPE = 'system_menu_type',
|
||||
SYSTEM_ROLE_TYPE = 'system_role_type',
|
||||
SYSTEM_DATA_SCOPE = 'system_data_scope',
|
||||
SYSTEM_NOTICE_TYPE = 'system_notice_type',
|
||||
SYSTEM_OPERATE_TYPE = 'system_operate_type',
|
||||
SYSTEM_LOGIN_TYPE = 'system_login_type',
|
||||
SYSTEM_LOGIN_RESULT = 'system_login_result',
|
||||
SYSTEM_SMS_CHANNEL_CODE = 'system_sms_channel_code',
|
||||
SYSTEM_SMS_TEMPLATE_TYPE = 'system_sms_template_type',
|
||||
SYSTEM_SMS_SEND_STATUS = 'system_sms_send_status',
|
||||
SYSTEM_SMS_RECEIVE_STATUS = 'system_sms_receive_status',
|
||||
SYSTEM_ERROR_CODE_TYPE = 'system_error_code_type',
|
||||
SYSTEM_OAUTH2_GRANT_TYPE = 'system_oauth2_grant_type',
|
||||
SYSTEM_MAIL_SEND_STATUS = 'system_mail_send_status',
|
||||
SYSTEM_NOTIFY_TEMPLATE_TYPE = 'system_notify_template_type',
|
||||
SYSTEM_SOCIAL_TYPE = 'system_social_type',
|
||||
|
||||
// ========== INFRA 模块 ==========
|
||||
INFRA_BOOLEAN_STRING = 'infra_boolean_string',
|
||||
INFRA_JOB_STATUS = 'infra_job_status',
|
||||
INFRA_JOB_LOG_STATUS = 'infra_job_log_status',
|
||||
INFRA_API_ERROR_LOG_PROCESS_STATUS = 'infra_api_error_log_process_status',
|
||||
INFRA_CONFIG_TYPE = 'infra_config_type',
|
||||
INFRA_CODEGEN_TEMPLATE_TYPE = 'infra_codegen_template_type',
|
||||
INFRA_CODEGEN_FRONT_TYPE = 'infra_codegen_front_type',
|
||||
INFRA_CODEGEN_SCENE = 'infra_codegen_scene',
|
||||
INFRA_FILE_STORAGE = 'infra_file_storage',
|
||||
// ========== INFRA 模块 ==========
|
||||
INFRA_BOOLEAN_STRING = 'infra_boolean_string',
|
||||
INFRA_JOB_STATUS = 'infra_job_status',
|
||||
INFRA_JOB_LOG_STATUS = 'infra_job_log_status',
|
||||
INFRA_API_ERROR_LOG_PROCESS_STATUS = 'infra_api_error_log_process_status',
|
||||
INFRA_CONFIG_TYPE = 'infra_config_type',
|
||||
INFRA_CODEGEN_TEMPLATE_TYPE = 'infra_codegen_template_type',
|
||||
INFRA_CODEGEN_FRONT_TYPE = 'infra_codegen_front_type',
|
||||
INFRA_CODEGEN_SCENE = 'infra_codegen_scene',
|
||||
INFRA_FILE_STORAGE = 'infra_file_storage',
|
||||
|
||||
// ========== BPM 模块 ==========
|
||||
BPM_MODEL_CATEGORY = 'bpm_model_category',
|
||||
BPM_MODEL_FORM_TYPE = 'bpm_model_form_type',
|
||||
BPM_TASK_ASSIGN_RULE_TYPE = 'bpm_task_assign_rule_type',
|
||||
BPM_PROCESS_INSTANCE_STATUS = 'bpm_process_instance_status',
|
||||
BPM_PROCESS_INSTANCE_RESULT = 'bpm_process_instance_result',
|
||||
BPM_TASK_ASSIGN_SCRIPT = 'bpm_task_assign_script',
|
||||
BPM_OA_LEAVE_TYPE = 'bpm_oa_leave_type',
|
||||
// ========== BPM 模块 ==========
|
||||
BPM_MODEL_CATEGORY = 'bpm_model_category',
|
||||
BPM_MODEL_FORM_TYPE = 'bpm_model_form_type',
|
||||
BPM_TASK_ASSIGN_RULE_TYPE = 'bpm_task_assign_rule_type',
|
||||
BPM_PROCESS_INSTANCE_STATUS = 'bpm_process_instance_status',
|
||||
BPM_PROCESS_INSTANCE_RESULT = 'bpm_process_instance_result',
|
||||
BPM_TASK_ASSIGN_SCRIPT = 'bpm_task_assign_script',
|
||||
BPM_OA_LEAVE_TYPE = 'bpm_oa_leave_type',
|
||||
|
||||
// ========== PAY 模块 ==========
|
||||
PAY_CHANNEL_CODE = 'pay_channel_code', // 支付渠道编码类型
|
||||
PAY_ORDER_STATUS = 'pay_order_status', // 商户支付订单状态
|
||||
PAY_REFUND_STATUS = 'pay_refund_status', // 退款订单状态
|
||||
PAY_NOTIFY_STATUS = 'pay_notify_status', // 商户支付回调状态
|
||||
PAY_NOTIFY_TYPE = 'pay_notify_type', // 商户支付回调状态
|
||||
PAY_TRANSFER_STATUS = 'pay_transfer_status', // 转账订单状态
|
||||
PAY_TRANSFER_TYPE = 'pay_transfer_type', // 转账订单状态
|
||||
// ========== PAY 模块 ==========
|
||||
PAY_CHANNEL_CODE = 'pay_channel_code', // 支付渠道编码类型
|
||||
PAY_ORDER_STATUS = 'pay_order_status', // 商户支付订单状态
|
||||
PAY_REFUND_STATUS = 'pay_refund_status', // 退款订单状态
|
||||
PAY_NOTIFY_STATUS = 'pay_notify_status', // 商户支付回调状态
|
||||
PAY_NOTIFY_TYPE = 'pay_notify_type', // 商户支付回调状态
|
||||
PAY_TRANSFER_STATUS = 'pay_transfer_status', // 转账订单状态
|
||||
PAY_TRANSFER_TYPE = 'pay_transfer_type', // 转账订单状态
|
||||
|
||||
// ========== MP 模块 ==========
|
||||
MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型
|
||||
MP_MESSAGE_TYPE = 'mp_message_type', // 消息类型
|
||||
// ========== MP 模块 ==========
|
||||
MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型
|
||||
MP_MESSAGE_TYPE = 'mp_message_type', // 消息类型
|
||||
|
||||
// ========== MALL - 会员模块 ==========
|
||||
MEMBER_POINT_BIZ_TYPE = 'member_point_biz_type', // 积分的业务类型
|
||||
MEMBER_EXPERIENCE_BIZ_TYPE = 'member_experience_biz_type', // 会员经验业务类型
|
||||
// ========== MALL - 会员模块 ==========
|
||||
MEMBER_POINT_BIZ_TYPE = 'member_point_biz_type', // 积分的业务类型
|
||||
MEMBER_EXPERIENCE_BIZ_TYPE = 'member_experience_biz_type', // 会员经验业务类型
|
||||
|
||||
// ========== MALL - 商品模块 ==========
|
||||
PRODUCT_UNIT = 'product_unit', // 商品单位
|
||||
PRODUCT_SPU_STATUS = 'product_spu_status', //商品状态
|
||||
PROMOTION_TYPE_ENUM = 'promotion_type_enum', // 营销类型枚举
|
||||
// ========== MALL - 商品模块 ==========
|
||||
PRODUCT_UNIT = 'product_unit', // 商品单位
|
||||
PRODUCT_SPU_STATUS = 'product_spu_status', //商品状态
|
||||
PROMOTION_TYPE_ENUM = 'promotion_type_enum', // 营销类型枚举
|
||||
|
||||
// ========== MALL - 交易模块 ==========
|
||||
EXPRESS_CHARGE_MODE = 'trade_delivery_express_charge_mode', //快递的计费方式
|
||||
TRADE_AFTER_SALE_STATUS = 'trade_after_sale_status', // 售后 - 状态
|
||||
TRADE_AFTER_SALE_WAY = 'trade_after_sale_way', // 售后 - 方式
|
||||
TRADE_AFTER_SALE_TYPE = 'trade_after_sale_type', // 售后 - 类型
|
||||
TRADE_ORDER_TYPE = 'trade_order_type', // 订单 - 类型
|
||||
TRADE_ORDER_STATUS = 'trade_order_status', // 订单 - 状态
|
||||
TRADE_ORDER_ITEM_AFTER_SALE_STATUS = 'trade_order_item_after_sale_status', // 订单项 - 售后状态
|
||||
TRADE_DELIVERY_TYPE = 'trade_delivery_type', // 配送方式
|
||||
BROKERAGE_ENABLED_CONDITION = 'brokerage_enabled_condition', // 分佣模式
|
||||
BROKERAGE_BIND_MODE = 'brokerage_bind_mode', // 分销关系绑定模式
|
||||
BROKERAGE_BANK_NAME = 'brokerage_bank_name', // 佣金提现银行
|
||||
BROKERAGE_WITHDRAW_TYPE = 'brokerage_withdraw_type', // 佣金提现类型
|
||||
BROKERAGE_RECORD_BIZ_TYPE = 'brokerage_record_biz_type', // 佣金业务类型
|
||||
BROKERAGE_RECORD_STATUS = 'brokerage_record_status', // 佣金状态
|
||||
BROKERAGE_WITHDRAW_STATUS = 'brokerage_withdraw_status', // 佣金提现状态
|
||||
// ========== MALL - 交易模块 ==========
|
||||
EXPRESS_CHARGE_MODE = 'trade_delivery_express_charge_mode', //快递的计费方式
|
||||
TRADE_AFTER_SALE_STATUS = 'trade_after_sale_status', // 售后 - 状态
|
||||
TRADE_AFTER_SALE_WAY = 'trade_after_sale_way', // 售后 - 方式
|
||||
TRADE_AFTER_SALE_TYPE = 'trade_after_sale_type', // 售后 - 类型
|
||||
TRADE_ORDER_TYPE = 'trade_order_type', // 订单 - 类型
|
||||
TRADE_ORDER_STATUS = 'trade_order_status', // 订单 - 状态
|
||||
TRADE_ORDER_ITEM_AFTER_SALE_STATUS = 'trade_order_item_after_sale_status', // 订单项 - 售后状态
|
||||
TRADE_DELIVERY_TYPE = 'trade_delivery_type', // 配送方式
|
||||
BROKERAGE_ENABLED_CONDITION = 'brokerage_enabled_condition', // 分佣模式
|
||||
BROKERAGE_BIND_MODE = 'brokerage_bind_mode', // 分销关系绑定模式
|
||||
BROKERAGE_BANK_NAME = 'brokerage_bank_name', // 佣金提现银行
|
||||
BROKERAGE_WITHDRAW_TYPE = 'brokerage_withdraw_type', // 佣金提现类型
|
||||
BROKERAGE_RECORD_BIZ_TYPE = 'brokerage_record_biz_type', // 佣金业务类型
|
||||
BROKERAGE_RECORD_STATUS = 'brokerage_record_status', // 佣金状态
|
||||
BROKERAGE_WITHDRAW_STATUS = 'brokerage_withdraw_status', // 佣金提现状态
|
||||
|
||||
// ========== MALL - 营销模块 ==========
|
||||
PROMOTION_DISCOUNT_TYPE = 'promotion_discount_type', // 优惠类型
|
||||
PROMOTION_PRODUCT_SCOPE = 'promotion_product_scope', // 营销的商品范围
|
||||
PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE = 'promotion_coupon_template_validity_type', // 优惠劵模板的有限期类型
|
||||
PROMOTION_COUPON_STATUS = 'promotion_coupon_status', // 优惠劵的状态
|
||||
PROMOTION_COUPON_TAKE_TYPE = 'promotion_coupon_take_type', // 优惠劵的领取方式
|
||||
PROMOTION_ACTIVITY_STATUS = 'promotion_activity_status', // 优惠活动的状态
|
||||
PROMOTION_CONDITION_TYPE = 'promotion_condition_type', // 营销的条件类型枚举
|
||||
PROMOTION_BARGAIN_RECORD_STATUS = 'promotion_bargain_record_status', // 砍价记录的状态
|
||||
PROMOTION_COMBINATION_RECORD_STATUS = 'promotion_combination_record_status', // 拼团记录的状态
|
||||
PROMOTION_BANNER_POSITION = 'promotion_banner_position', // banner 定位
|
||||
// ========== MALL - 营销模块 ==========
|
||||
PROMOTION_DISCOUNT_TYPE = 'promotion_discount_type', // 优惠类型
|
||||
PROMOTION_PRODUCT_SCOPE = 'promotion_product_scope', // 营销的商品范围
|
||||
PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE = 'promotion_coupon_template_validity_type', // 优惠劵模板的有限期类型
|
||||
PROMOTION_COUPON_STATUS = 'promotion_coupon_status', // 优惠劵的状态
|
||||
PROMOTION_COUPON_TAKE_TYPE = 'promotion_coupon_take_type', // 优惠劵的领取方式
|
||||
PROMOTION_ACTIVITY_STATUS = 'promotion_activity_status', // 优惠活动的状态
|
||||
PROMOTION_CONDITION_TYPE = 'promotion_condition_type', // 营销的条件类型枚举
|
||||
PROMOTION_BARGAIN_RECORD_STATUS = 'promotion_bargain_record_status', // 砍价记录的状态
|
||||
PROMOTION_COMBINATION_RECORD_STATUS = 'promotion_combination_record_status', // 拼团记录的状态
|
||||
PROMOTION_BANNER_POSITION = 'promotion_banner_position', // banner 定位
|
||||
|
||||
// ========== CRM - 客户管理模块 ==========
|
||||
CRM_RECEIVABLE_CHECK_STATUS = 'crm_receivable_check_status',
|
||||
CRM_RETURN_TYPE = 'crm_return_type',
|
||||
CRM_CUSTOMER_INDUSTRY = 'crm_customer_industry',
|
||||
CRM_CUSTOMER_LEVEL = 'crm_customer_level',
|
||||
CRM_CUSTOMER_SOURCE = 'crm_customer_source',
|
||||
CRM_PRODUCT_STATUS = 'crm_product_status'
|
||||
// ========== CRM - 客户管理模块 ==========
|
||||
CRM_RECEIVABLE_CHECK_STATUS = 'crm_receivable_check_status',
|
||||
CRM_RETURN_TYPE = 'crm_return_type',
|
||||
CRM_CUSTOMER_INDUSTRY = 'crm_customer_industry',
|
||||
CRM_CUSTOMER_LEVEL = 'crm_customer_level',
|
||||
CRM_CUSTOMER_SOURCE = 'crm_customer_source',
|
||||
CRM_PRODUCT_STATUS = 'crm_product_status',
|
||||
|
||||
// ========== CRM - 数据权限模块 ==========
|
||||
CRM_BIZ_TYPE = 'crm_biz_type', // 数据模块类型
|
||||
CRM_PERMISSION_LEVEL = 'crm_permission_level' // 用户数据权限类型
|
||||
}
|
||||
|
@ -335,5 +335,8 @@ export function getDateRange(
|
||||
beginDate: dayjs.ConfigType,
|
||||
endDate: dayjs.ConfigType
|
||||
): [string, string] {
|
||||
return [dayjs(beginDate).startOf('d').toString(), dayjs(endDate).endOf('d').toString()]
|
||||
return [
|
||||
dayjs(beginDate).startOf('d').format('YYYY-MM-DD HH:mm:ss'),
|
||||
dayjs(endDate).endOf('d').format('YYYY-MM-DD HH:mm:ss')
|
||||
]
|
||||
}
|
||||
|
@ -19,9 +19,14 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="权限级别" prop="level">
|
||||
<el-radio-group v-model="formData.level">
|
||||
<!-- TODO @puhui999:搞个字典配置?然后这里 remove 掉负责人 -->
|
||||
<el-radio :label="CrmPermissionLevelEnum.READ">只读</el-radio>
|
||||
<el-radio :label="CrmPermissionLevelEnum.WRITE">读写</el-radio>
|
||||
<template
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_PERMISSION_LEVEL)"
|
||||
:key="dict.value"
|
||||
>
|
||||
<el-radio v-if="dict.value != CrmPermissionLevelEnum.OWNER" :label="dict.value">
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</template>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@ -34,7 +39,8 @@
|
||||
<script lang="ts" setup>
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import * as PermissionApi from '@/api/crm/permission'
|
||||
import { CrmPermissionLevelEnum } from './index'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { CrmPermissionLevelEnum } from '@/views/crm/components/index'
|
||||
|
||||
defineOptions({ name: 'CrmPermissionForm' })
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<!-- 操作栏 -->
|
||||
<el-row justify="end">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
<el-button type="primary" @click="openForm">
|
||||
<Icon class="mr-5px" icon="ep:plus" />
|
||||
新增
|
||||
</el-button>
|
||||
@ -9,7 +9,7 @@
|
||||
<Icon class="mr-5px" icon="ep:edit" />
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button @click="handleRemove">
|
||||
<el-button @click="handleDelete">
|
||||
<Icon class="mr-5px" icon="ep:delete" />
|
||||
移除
|
||||
</el-button>
|
||||
@ -30,45 +30,32 @@
|
||||
<el-table-column align="center" label="岗位" prop="postNames" />
|
||||
<el-table-column align="center" label="权限级别" prop="level">
|
||||
<template #default="{ row }">
|
||||
<el-tag>{{ getLevelName(row.level) }}</el-tag>
|
||||
<dict-tag :type="DICT_TYPE.CRM_PERMISSION_LEVEL" :value="row.level" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :formatter="dateFormatter" align="center" label="加入时间" prop="createTime" />
|
||||
</el-table>
|
||||
<CrmPermissionForm ref="crmPermissionFormRef" />
|
||||
<CrmPermissionForm ref="permissionFormRef" @success="getList" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// TODO @puhui999:改成 CrmPermissionList
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import { ElTable } from 'element-plus'
|
||||
import * as PermissionApi from '@/api/crm/permission'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
import CrmPermissionForm from './CrmPermissionForm.vue'
|
||||
import { CrmPermissionLevelEnum } from './index'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
|
||||
defineOptions({ name: 'CrmTeam' })
|
||||
defineOptions({ name: 'CrmPermissionList' })
|
||||
|
||||
const message = useMessage() // 消息
|
||||
|
||||
const props = defineProps<{
|
||||
bizType: number
|
||||
bizId: number
|
||||
bizType: number // 模块类型
|
||||
bizId: number // 模块数据编号
|
||||
}>()
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const list = ref<PermissionApi.PermissionVO[]>([
|
||||
// TODO 测试数据
|
||||
{
|
||||
id: 1, // 数据权限编号
|
||||
userId: 1, // 用户编号
|
||||
bizType: 1, // Crm 类型
|
||||
bizId: 1, // Crm 类型数据编号
|
||||
level: 1, // 权限级别
|
||||
deptName: '研发部门', // 部门名称
|
||||
nickname: '芋道源码', // 用户昵称
|
||||
postNames: '全栈开发工程师', // 岗位名称数组
|
||||
createTime: new Date()
|
||||
}
|
||||
]) // 列表的数据
|
||||
const list = ref<PermissionApi.PermissionVO[]>([]) // 列表的数据
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
@ -83,40 +70,28 @@ const getList = async () => {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @puhui999:字典格式化
|
||||
/**
|
||||
* 获得权限级别名称
|
||||
* @param level 权限级别
|
||||
*/
|
||||
const getLevelName = computed(() => (level: number) => {
|
||||
switch (level) {
|
||||
case CrmPermissionLevelEnum.OWNER:
|
||||
return '负责人'
|
||||
case CrmPermissionLevelEnum.READ:
|
||||
return '只读'
|
||||
case CrmPermissionLevelEnum.WRITE:
|
||||
return '读写'
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
// TODO @puhui999:空行稍微注意下哈;一些注释补齐下;
|
||||
const multipleSelection = ref<PermissionApi.PermissionVO[]>([])
|
||||
const multipleSelection = ref<PermissionApi.PermissionVO[]>([]) // 选择的团队成员
|
||||
const handleSelectionChange = (val: PermissionApi.PermissionVO[]) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
// TODO @puhui999:一些变量命名,看看有没可能跟列表界面的 index.vue 保持他统一的风格;
|
||||
const crmPermissionFormRef = ref<InstanceType<typeof CrmPermissionForm>>()
|
||||
|
||||
const permissionFormRef = ref<InstanceType<typeof CrmPermissionForm>>() // 权限表单 Ref
|
||||
/**
|
||||
* 编辑团队成员
|
||||
*/
|
||||
const handleEdit = () => {
|
||||
if (multipleSelection.value?.length === 0) {
|
||||
message.warning('请先选择团队成员后操作!')
|
||||
return
|
||||
}
|
||||
const ids = multipleSelection.value?.map((item) => item.id)
|
||||
crmPermissionFormRef.value?.open('update', props.bizType, props.bizId, ids)
|
||||
permissionFormRef.value?.open('update', props.bizType, props.bizId, ids)
|
||||
}
|
||||
const handleRemove = async () => {
|
||||
|
||||
/**
|
||||
* 移除团队成员
|
||||
*/
|
||||
const handleDelete = async () => {
|
||||
if (multipleSelection.value?.length === 0) {
|
||||
message.warning('请先选择团队成员后操作!')
|
||||
return
|
||||
@ -129,11 +104,18 @@ const handleRemove = async () => {
|
||||
ids
|
||||
})
|
||||
}
|
||||
const handleAdd = () => {
|
||||
crmPermissionFormRef.value?.open('create', props.bizType, props.bizId)
|
||||
|
||||
/**
|
||||
* 添加团队成员
|
||||
*/
|
||||
const openForm = () => {
|
||||
permissionFormRef.value?.open('create', props.bizType, props.bizId)
|
||||
}
|
||||
|
||||
const userStore = useUserStoreWithOut()
|
||||
const userStore = useUserStoreWithOut() // 用户信息缓存
|
||||
/**
|
||||
* 退出团队
|
||||
*/
|
||||
const handleQuit = async () => {
|
||||
const permission = list.value.find(
|
||||
(item) => item.userId === userStore.getUser.id && item.level === CrmPermissionLevelEnum.OWNER
|
@ -1,4 +1,4 @@
|
||||
import CrmTeam from './CrmTeamList.vue'
|
||||
import CrmPermissionList from './CrmPermissionList.vue'
|
||||
|
||||
enum CrmBizTypeEnum {
|
||||
CRM_LEADS = 1, // 线索
|
||||
@ -9,9 +9,7 @@ enum CrmBizTypeEnum {
|
||||
}
|
||||
|
||||
enum CrmPermissionLevelEnum {
|
||||
OWNER = 1, // 负责人
|
||||
READ = 2, // 读
|
||||
WRITE = 3 // 写
|
||||
OWNER = 1 // 负责人
|
||||
}
|
||||
|
||||
export { CrmTeam, CrmBizTypeEnum, CrmPermissionLevelEnum }
|
||||
export { CrmPermissionList, CrmBizTypeEnum, CrmPermissionLevelEnum }
|
||||
|
@ -1,170 +1,131 @@
|
||||
<template>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" :width="800">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="130px"
|
||||
v-loading="formLoading"
|
||||
:inline="true"
|
||||
>
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="请输入姓名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="负责人" prop="ownerUserId">
|
||||
<el-select
|
||||
v-model="ownerUserList"
|
||||
placeholder="请选择负责人"
|
||||
multiple
|
||||
value-key="id"
|
||||
lable-key="nickname"
|
||||
@click="openOwerForm('open')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in ownerUserList"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- TODO 芋艿:封装成一个组件 -->
|
||||
<el-form-item label="客户名称" prop="customerName">
|
||||
<el-popover
|
||||
placement="bottom"
|
||||
:width="600"
|
||||
trigger="click"
|
||||
:teleported="false"
|
||||
:visible="showCustomer"
|
||||
:offset="10"
|
||||
>
|
||||
<template #reference>
|
||||
<el-input
|
||||
placeholder="请选择"
|
||||
@click="openCustomerSelect"
|
||||
v-model="formData.customerName"
|
||||
/>
|
||||
</template>
|
||||
<el-table :data="list" ref="multipleTableRef" @select="handleSelectionChange">
|
||||
<el-table-column label="选择" type="selection" width="55" />
|
||||
<el-table-column width="100" property="id" label="编号" />
|
||||
<el-table-column width="150" property="name" label="客户名称" />
|
||||
<el-table-column label="客户来源" align="center" prop="source" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="客户等级" align="center" prop="level" width="120">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col>
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
layout="sizes, prev, pager, next"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="10" :offset="13">
|
||||
<el-button @click="selectCustomer">确认</el-button>
|
||||
<el-button @click="showCustomer = false">取消</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
<el-form-item label="性别" prop="sex">
|
||||
<el-select v-model="formData.sex" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="mobile">
|
||||
<el-input v-model="formData.mobile" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="座机" prop="telephone">
|
||||
<el-input v-model="formData.telephone" placeholder="请输入座机" style="width: 215px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="formData.email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item label="QQ" prop="qq">
|
||||
<el-input v-model="formData.qq" placeholder="请输入QQ" style="width: 215px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="微信" prop="webchat">
|
||||
<el-input v-model="formData.webchat" placeholder="请输入微信" />
|
||||
</el-form-item>
|
||||
<el-form-item label="下次联系时间" prop="nextTime">
|
||||
<el-date-picker
|
||||
v-model="formData.nextTime"
|
||||
type="date"
|
||||
value-format="x"
|
||||
placeholder="选择下次联系时间"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="地址" prop="address">
|
||||
<el-input v-model="formData.address" placeholder="请输入地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="直属上级" prop="parentId">
|
||||
<el-select v-model="formData.parentId" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in allContactList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
:disabled="item.id == formData.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="职位" prop="post">
|
||||
<el-input v-model="formData.post" placeholder="请输入职位" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="是否关键决策人" prop="policyMakers" style="width: 400px">
|
||||
<el-radio-group v-model="formData.policyMakers">
|
||||
<el-radio
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
<Dialog :title="dialogTitle" v-model="dialogVisible" :width="820">
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="110px" v-loading="formLoading">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input input-style="width:190px;" v-model="formData.name" placeholder="请输入姓名" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="负责人" prop="ownerUserId">
|
||||
<el-select v-model="formData.ownerUserId" placeholder="请选择负责人" value-key="id" lable-key="nickname">
|
||||
<el-option v-for="item in userList" :key="item.id" :label="item.nickname" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="客户名称" prop="customerName">
|
||||
<el-select v-model="formData.customerId" placeholder="请选择客户" value-key="id" lable-key="name">
|
||||
<el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12"><el-form-item label="性别" prop="sex">
|
||||
<el-select v-model="formData.sex" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)" :key="dict.value"
|
||||
:label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机号" prop="mobile">
|
||||
<el-input input-style="width:190px;" v-model="formData.mobile" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="座机" prop="telephone">
|
||||
<el-input v-model="formData.telephone" placeholder="请输入座机" style="width: 215px" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input input-style="width:190px;" v-model="formData.email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="QQ" prop="qq">
|
||||
<el-input v-model="formData.qq" placeholder="请输入QQ" style="width: 215px" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="微信" prop="wechat">
|
||||
<el-input input-style="width:190px;" v-model="formData.wechat" placeholder="请输入微信" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12"> <el-form-item label="下次联系时间" prop="nextTime">
|
||||
<el-date-picker v-model="formData.nextTime" type="date" value-format="x" placeholder="选择下次联系时间" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="所在地" prop="areaId">
|
||||
<el-tree-select
|
||||
v-model="formData.areaId" :data="areaList" :props="defaultProps"
|
||||
:render-after-expand="true" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="地址" prop="address">
|
||||
<el-input input-style="width:190px;" v-model="formData.address" placeholder="请输入地址" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row><el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="直属上级" prop="parentId">
|
||||
<el-select v-model="formData.parentId" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in allContactList" :key="item.id" :label="item.name" :value="item.id"
|
||||
:disabled="item.id == formData.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12"> <el-form-item label="职位" prop="post">
|
||||
<el-input input-style="width:190px;" v-model="formData.post" placeholder="请输入职位" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row><el-row>
|
||||
<el-col :span="12"><el-form-item label="是否关键决策人" prop="master" style="width: 400px">
|
||||
<el-radio-group v-model="formData.master">
|
||||
<el-radio
|
||||
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" :key="dict.value"
|
||||
:label="dict.value">
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24"><el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
<OwerSelect
|
||||
ref="owerRef"
|
||||
@confirmOwerSelect="owerSelectValue"
|
||||
:initOwerUser="formData.ownerUserId"
|
||||
/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as ContactApi from '@/api/crm/contact'
|
||||
import { DICT_TYPE, getIntDictOptions, getBoolDictOptions } from '@/utils/dict'
|
||||
import OwerSelect from './OwerSelect.vue'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import { ElTable } from 'element-plus'
|
||||
import * as AreaApi from '@/api/system/area'
|
||||
import { defaultProps } from '@/utils/tree'
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
@ -172,6 +133,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const areaList = ref([]) // 地区列表
|
||||
const formData = ref({
|
||||
nextTime: undefined,
|
||||
mobile: undefined,
|
||||
@ -188,21 +150,10 @@ const formData = ref({
|
||||
name: undefined,
|
||||
post: undefined,
|
||||
qq: undefined,
|
||||
webchat: undefined,
|
||||
wechat: undefined,
|
||||
sex: undefined,
|
||||
policyMakers: undefined
|
||||
})
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: null,
|
||||
mobile: null,
|
||||
industryId: null,
|
||||
level: null,
|
||||
source: null
|
||||
master: false,
|
||||
areaId: undefined
|
||||
})
|
||||
const formRules = reactive({
|
||||
name: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
|
||||
@ -212,56 +163,33 @@ const formRules = reactive({
|
||||
const formRef = ref() // 表单 Ref
|
||||
const ownerUserList = ref<any[]>([])
|
||||
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
|
||||
const customerList = ref<CustomerApi.CustomerVO[]>([]) // 客户列表
|
||||
const allContactList = ref([]) // 所有联系人列表
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
allContactList.value = await ContactApi.simpleAlllist()
|
||||
resetForm()
|
||||
allContactList.value = await ContactApi.simpleAllList()
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
customerList.value = await CustomerApi.queryAllList()
|
||||
areaList.value = await AreaApi.getAreaTree()
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await ContactApi.getContact(id)
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
await gotOwnerUser(formData.value.ownerUserId)
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await CustomerApi.getCustomerPage(queryParams)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const gotOwnerUser = (owerUserId: any) => {
|
||||
if (owerUserId !== null) {
|
||||
owerUserId.split(',').forEach((item: string) => {
|
||||
userList.value.find((user: { id: any }) => {
|
||||
if (user.id == item) {
|
||||
ownerUserList.value.push(user)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
owerSelectValue(ownerUserList)
|
||||
// owerSelectValue(ownerUserList)
|
||||
// 校验表单
|
||||
if (!formRef) return
|
||||
const valid = await formRef.value.validate()
|
||||
@ -302,52 +230,11 @@ const resetForm = () => {
|
||||
name: undefined,
|
||||
post: undefined,
|
||||
qq: undefined,
|
||||
webchat: undefined,
|
||||
wechat: undefined,
|
||||
sex: undefined,
|
||||
policyMakers: undefined
|
||||
master: false
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
ownerUserList.value = []
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
// TODO @zyna:owner?拼写要注意哈;
|
||||
const owerRef = ref()
|
||||
const openOwerForm = (type: string) => {
|
||||
owerRef.value.open(type, ownerUserList.value)
|
||||
}
|
||||
const owerSelectValue = (value) => {
|
||||
ownerUserList.value = value.value
|
||||
formData.value.ownerUserId = undefined
|
||||
value.value.forEach((item, index) => {
|
||||
if (index != 0) {
|
||||
formData.value.ownerUserId = formData.value.ownerUserId + ',' + item.id
|
||||
} else {
|
||||
formData.value.ownerUserId = item.id
|
||||
}
|
||||
})
|
||||
}
|
||||
// 选择客户
|
||||
const showCustomer = ref(false)
|
||||
const openCustomerSelect = () => {
|
||||
showCustomer.value = !showCustomer.value
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
const multipleTableRef = ref<InstanceType<typeof ElTable>>()
|
||||
const multipleSelection = ref()
|
||||
const handleSelectionChange = ({}, row) => {
|
||||
multipleSelection.value = row
|
||||
multipleTableRef.value!.clearSelection()
|
||||
multipleTableRef.value!.toggleRowSelection(row, undefined)
|
||||
}
|
||||
const selectCustomer = () => {
|
||||
formData.value.customerId = multipleSelection.value.id
|
||||
formData.value.customerName = multipleSelection.value.name
|
||||
showCustomer.value = !showCustomer.value
|
||||
}
|
||||
const allContactList = ref([]) // 所有联系人列表
|
||||
onMounted(async () => {
|
||||
allContactList.value = await ContactApi.simpleAlllist()
|
||||
})
|
||||
</script>
|
||||
|
@ -1,3 +1,10 @@
|
||||
<!--
|
||||
* @Author: zyna
|
||||
* @Date: 2023-11-26 10:39:46
|
||||
* @LastEditTime: 2023-11-26 20:43:43
|
||||
* @FilePath: \yudao-ui-admin-vue3\src\views\crm\contact\detail\ContactDetails.vue
|
||||
* @Description:
|
||||
-->
|
||||
<template>
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item name="basicInfo">
|
||||
@ -24,14 +31,17 @@
|
||||
{{ contact.qq }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="微信">
|
||||
{{ contact.webchat }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="详细地址">
|
||||
{{ contact.address }}
|
||||
{{ contact.wechat }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="下次联系时间">
|
||||
{{ contact.nextTime ? formatDate(contact.nextTime) : '空' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="所在地">
|
||||
{{ contact.areaName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="详细地址">
|
||||
{{ contact.address }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="contact.sex" />
|
||||
</el-descriptions-item>
|
||||
@ -46,7 +56,7 @@
|
||||
</template>
|
||||
<el-descriptions :column="2">
|
||||
<el-descriptions-item label="负责人">
|
||||
{{ gotOwnerUser(contact.ownerUserId) }}
|
||||
{{ contact.ownerUserName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建人">
|
||||
{{ contact.creatorName }}
|
||||
@ -66,29 +76,9 @@
|
||||
import * as ContactApi from '@/api/crm/contact'
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
const { contact } = defineProps<{ contact: ContactApi.ContactVO }>()
|
||||
|
||||
// 展示的折叠面板
|
||||
const activeNames = ref(['basicInfo', 'systemInfo'])
|
||||
const gotOwnerUser = (owerUserId: string) => {
|
||||
let ownerName = ''
|
||||
if (owerUserId !== null && owerUserId != undefined) {
|
||||
owerUserId.split(',').forEach((item: string, index: number) => {
|
||||
if (index != 0) {
|
||||
ownerName =
|
||||
ownerName + ',' + userList.value.find((user: { id: any }) => user.id == item)?.nickname
|
||||
} else {
|
||||
ownerName = userList.value.find((user: { id: any }) => user.id == item)?.nickname || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
return ownerName
|
||||
}
|
||||
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
|
@ -63,33 +63,18 @@
|
||||
<!-- TODO wanwan:这个 tab 拉满哈,可以更好看; -->
|
||||
<el-col :span="18">
|
||||
<el-tabs>
|
||||
<el-tab-pane label="详细资料">
|
||||
<el-tab-pane label="基本信息">
|
||||
<!-- TODO wanwan:这个 ml-2 是不是可以优化下,不要整个左移,而是里面的内容有个几 px 的偏移,不顶在框里 -->
|
||||
<ContactDetails class="ml-2" :contact="contact" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="活动" lazy> 活动</el-tab-pane>
|
||||
<el-tab-pane label="邮件" lazy> 邮件</el-tab-pane>
|
||||
<el-tab-pane label="工商信息" lazy> 工商信息</el-tab-pane>
|
||||
<!-- TODO wanwan 以下标签上的数量需要接口统计返回 -->
|
||||
<el-tab-pane label="客户" lazy>
|
||||
<template #label> 客户<el-badge :value="12" class="item" type="primary" /> </template>
|
||||
客户
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="团队成员" lazy>
|
||||
<template #label> 团队成员<el-badge :value="2" class="item" type="primary" /> </template>
|
||||
团队成员
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="跟进记录" lazy> 跟进记录</el-tab-pane>
|
||||
<el-tab-pane label="商机" lazy> 商机</el-tab-pane>
|
||||
<el-tab-pane label="合同" lazy>
|
||||
<template #label> 合同<el-badge :value="3" class="item" type="primary" /> </template>
|
||||
合同
|
||||
<el-tab-pane label="附件" lazy> 附件</el-tab-pane>
|
||||
<!-- TODO wanwan 以下标签上的数量需要接口统计返回 -->
|
||||
<el-tab-pane label="操作记录" lazy>
|
||||
<template #label> 操作记录<el-badge :value="12" class="item" type="primary" /> </template>
|
||||
操作记录
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="回款" lazy>
|
||||
<template #label> 回款<el-badge :value="4" class="item" type="primary" /> </template>
|
||||
回款
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="回访" lazy> 回访</el-tab-pane>
|
||||
<el-tab-pane label="发票" lazy> 发票</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-col>
|
||||
|
||||
|
@ -55,9 +55,9 @@
|
||||
class="!w-240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="微信" prop="webchat">
|
||||
<el-form-item label="微信" prop="wechat">
|
||||
<el-input
|
||||
v-model="queryParams.webchat"
|
||||
v-model="queryParams.wechat"
|
||||
placeholder="请输入微信"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
@ -109,20 +109,16 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="职位" align="center" prop="post" />
|
||||
<el-table-column label="是否关键决策人" align="center" prop="policyMakers">
|
||||
<el-table-column label="是否关键决策人" align="center" prop="master">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.policyMakers" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="直属上级" align="center" prop="parentId">
|
||||
<template #default="scope">
|
||||
{{ allContactList.find((contact) => contact.id === scope.row.parentId)?.name }}
|
||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="直属上级" align="center" prop="parentName"/>
|
||||
<el-table-column label="手机号" align="center" prop="mobile" />
|
||||
<el-table-column label="座机" align="center" prop="telephone" />
|
||||
<el-table-column label="QQ" align="center" prop="qq" />
|
||||
<el-table-column label="微信" align="center" prop="webchat" />
|
||||
<el-table-column label="微信" align="center" prop="wechat" />
|
||||
<el-table-column label="邮箱" align="center" prop="email" />
|
||||
<el-table-column label="地址" align="center" prop="address" />
|
||||
<el-table-column
|
||||
@ -142,7 +138,7 @@
|
||||
/>
|
||||
<el-table-column label="负责人" align="center" prop="ownerUserId">
|
||||
<template #default="scope">
|
||||
{{ gotOwnerUser(scope.row.ownerUserId) }}
|
||||
{{ scope.row.ownerUserName}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column label="所属部门" align="center" prop="ownerUserId" /> -->
|
||||
@ -239,13 +235,12 @@ const queryParams = reactive({
|
||||
name: null,
|
||||
post: null,
|
||||
qq: null,
|
||||
webchat: null,
|
||||
wechat: null,
|
||||
sex: null,
|
||||
policyMakers: null
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
@ -305,35 +300,15 @@ const handleExport = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @zyna:这个负责人的读取,放在后端好点
|
||||
const gotOwnerUser = (owerUserId: string) => {
|
||||
let ownerName = ''
|
||||
if (owerUserId !== null) {
|
||||
owerUserId.split(',').forEach((item: string, index: number) => {
|
||||
if (index != 0) {
|
||||
ownerName =
|
||||
ownerName + ',' + userList.value.find((user: { id: any }) => user.id == item)?.nickname
|
||||
} else {
|
||||
ownerName = userList.value.find((user: { id: any }) => user.id == item)?.nickname || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
return ownerName
|
||||
}
|
||||
|
||||
/** 打开客户详情 */
|
||||
const { push } = useRouter()
|
||||
const openDetail = (id: number) => {
|
||||
push({ name: 'CrmContactDetail', params: { id } })
|
||||
}
|
||||
|
||||
// TODO @zyna:这个上级的读取,放在后端读取,更合适;因为可能数据量比较大
|
||||
const allContactList = ref([]) //所有联系人列表
|
||||
const allCustomerList = ref([]) //客户列表
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
allContactList.value = await ContactApi.simpleAlllist()
|
||||
})
|
||||
</script>
|
||||
|
@ -8,7 +8,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<!-- 右上:按钮 -->
|
||||
<el-button @click="openForm('update', customer.id)" v-hasPermi="['crm:customer:update']">
|
||||
<el-button v-hasPermi="['crm:customer:update']" @click="openForm('update', customer.id)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button>更改成交状态</el-button>
|
||||
@ -16,31 +16,31 @@
|
||||
</div>
|
||||
<el-row class="mt-10px">
|
||||
<el-button>
|
||||
<Icon icon="ph:calendar-fill" class="mr-5px" />
|
||||
<Icon class="mr-5px" icon="ph:calendar-fill" />
|
||||
创建任务
|
||||
</el-button>
|
||||
<el-button>
|
||||
<Icon icon="carbon:email" class="mr-5px" />
|
||||
<Icon class="mr-5px" icon="carbon:email" />
|
||||
发送邮件
|
||||
</el-button>
|
||||
<el-button>
|
||||
<Icon icon="system-uicons:contacts" class="mr-5px" />
|
||||
<Icon class="mr-5px" icon="system-uicons:contacts" />
|
||||
创建联系人
|
||||
</el-button>
|
||||
<el-button>
|
||||
<Icon icon="ep:opportunity" class="mr-5px" />
|
||||
<Icon class="mr-5px" icon="ep:opportunity" />
|
||||
创建商机
|
||||
</el-button>
|
||||
<el-button>
|
||||
<Icon icon="clarity:contract-line" class="mr-5px" />
|
||||
<Icon class="mr-5px" icon="clarity:contract-line" />
|
||||
创建合同
|
||||
</el-button>
|
||||
<el-button>
|
||||
<Icon icon="icon-park:income-one" class="mr-5px" />
|
||||
<Icon class="mr-5px" icon="icon-park:income-one" />
|
||||
创建回款
|
||||
</el-button>
|
||||
<el-button>
|
||||
<Icon icon="fluent:people-team-add-20-filled" class="mr-5px" />
|
||||
<Icon class="mr-5px" icon="fluent:people-team-add-20-filled" />
|
||||
添加团队成员
|
||||
</el-button>
|
||||
</el-row>
|
||||
@ -75,20 +75,32 @@
|
||||
<el-tab-pane label="客户关系" lazy> 客户关系</el-tab-pane>
|
||||
<!-- TODO wanwan 以下标签上的数量需要接口统计返回 -->
|
||||
<el-tab-pane label="联系人" lazy>
|
||||
<template #label> 联系人<el-badge class="item" type="primary" /> </template>
|
||||
<template #label>
|
||||
联系人
|
||||
<el-badge class="item" type="primary" />
|
||||
</template>
|
||||
联系人
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="团队成员" lazy>
|
||||
<template #label> 团队成员<el-badge class="item" type="primary" /> </template>
|
||||
团队成员
|
||||
<template #label>
|
||||
团队成员
|
||||
<el-badge class="item" type="primary" />
|
||||
</template>
|
||||
<CrmPermissionList :biz-id="customer.id" :biz-type="CrmBizTypeEnum.CRM_CUSTOMER" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="商机" lazy> 商机</el-tab-pane>
|
||||
<el-tab-pane label="合同" lazy>
|
||||
<template #label> 合同<el-badge class="item" type="primary" /> </template>
|
||||
<template #label>
|
||||
合同
|
||||
<el-badge class="item" type="primary" />
|
||||
</template>
|
||||
合同
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="回款" lazy>
|
||||
<template #label> 回款<el-badge class="item" type="primary" /> </template>
|
||||
<template #label>
|
||||
回款
|
||||
<el-badge class="item" type="primary" />
|
||||
</template>
|
||||
回款
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="回访" lazy> 回访</el-tab-pane>
|
||||
@ -100,7 +112,7 @@
|
||||
<CustomerForm ref="formRef" @success="getCustomerData(id)" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
@ -108,6 +120,7 @@ import CustomerBasicInfo from '@/views/crm/customer/detail/CustomerBasicInfo.vue
|
||||
import { DICT_TYPE } from '@/utils/dict'
|
||||
import CustomerDetails from '@/views/crm/customer/detail/CustomerDetails.vue'
|
||||
import CustomerForm from '@/views/crm/customer/CustomerForm.vue'
|
||||
import { CrmBizTypeEnum, CrmPermissionList } from '@/views/crm/components'
|
||||
|
||||
defineOptions({ name: 'CustomerDetail' })
|
||||
|
||||
|
@ -121,7 +121,7 @@
|
||||
<el-table-column align="center" label="手机" prop="mobile" width="120" />
|
||||
<el-table-column align="center" label="详细地址" prop="detailAddress" width="200" />
|
||||
<el-table-column align="center" label="负责人" prop="ownerUserName" />
|
||||
<el-table-column align="center" label="所属部门" prop="ownerUserDept" />
|
||||
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" />
|
||||
<el-table-column align="center" label="创建人" prop="creatorName" />
|
||||
<el-table-column
|
||||
:formatter="dateFormatter"
|
||||
@ -185,8 +185,6 @@
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
<!-- TODO 方便查看效果 TODO 芋艿:先注释了,避免演示环境报错 -->
|
||||
<!-- <CrmTeam :biz-id="1" :biz-type="CrmBizTypeEnum.CRM_CUSTOMER" />-->
|
||||
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<CustomerForm ref="formRef" @success="getList" />
|
||||
@ -198,7 +196,6 @@ import { dateFormatter } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
import * as CustomerApi from '@/api/crm/customer'
|
||||
import CustomerForm from './CustomerForm.vue'
|
||||
import { CrmBizTypeEnum, CrmTeam } from '@/views/crm/components'
|
||||
|
||||
defineOptions({ name: 'CrmCustomer' })
|
||||
|
||||
@ -211,11 +208,12 @@ const list = ref([]) // 列表的数据
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
name: null,
|
||||
mobile: null,
|
||||
industryId: null,
|
||||
level: null,
|
||||
source: null
|
||||
pool: false,
|
||||
name: '',
|
||||
mobile: '',
|
||||
industryId: undefined,
|
||||
level: undefined,
|
||||
source: undefined
|
||||
})
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const exportLoading = ref(false) // 导出的加载中
|
||||
@ -241,6 +239,7 @@ const handleQuery = () => {
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
queryParams.pool = false
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<!-- 左侧:建立连接、发送消息 -->
|
||||
<el-card :gutter="12" class="w-1/2" shadow="always">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
@ -11,28 +12,38 @@
|
||||
<el-tag :color="getTagColor">{{ status }}</el-tag>
|
||||
</div>
|
||||
<hr class="my-4" />
|
||||
|
||||
<div class="flex">
|
||||
<el-input v-model="server" disabled>
|
||||
<template #prepend> 服务地址</template>
|
||||
<template #prepend>服务地址</template>
|
||||
</el-input>
|
||||
<el-button :type="getIsOpen ? 'danger' : 'primary'" @click="toggle">
|
||||
<el-button :type="getIsOpen ? 'danger' : 'primary'" @click="toggleConnectStatus">
|
||||
{{ getIsOpen ? '关闭连接' : '开启连接' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<p class="mt-4 text-lg font-medium">设置</p>
|
||||
<p class="mt-4 text-lg font-medium">消息输入框</p>
|
||||
<hr class="my-4" />
|
||||
<el-input
|
||||
v-model="sendValue"
|
||||
v-model="sendText"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
:disabled="!getIsOpen"
|
||||
clearable
|
||||
type="textarea"
|
||||
placeholder="请输入你要发送的消息"
|
||||
/>
|
||||
<el-button :disabled="!getIsOpen" block class="mt-4" type="primary" @click="handlerSend">
|
||||
<el-select v-model="sendUserId" class="mt-4" placeholder="请选择发送人">
|
||||
<el-option key="" label="所有人" value="" />
|
||||
<el-option
|
||||
v-for="user in userList"
|
||||
:key="user.id"
|
||||
:label="user.nickname"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button :disabled="!getIsOpen" block class="ml-2 mt-4" type="primary" @click="handlerSend">
|
||||
发送
|
||||
</el-button>
|
||||
</el-card>
|
||||
<!-- 右侧:消息记录 -->
|
||||
<el-card :gutter="12" class="w-1/2" shadow="always">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
@ -41,13 +52,13 @@
|
||||
</template>
|
||||
<div class="max-h-80 overflow-auto">
|
||||
<ul>
|
||||
<li v-for="item in getList" :key="item.time" class="mt-2">
|
||||
<li v-for="msg in messageList.reverse()" :key="msg.time" class="mt-2">
|
||||
<div class="flex items-center">
|
||||
<span class="text-primary mr-2 font-medium">收到消息:</span>
|
||||
<span>{{ formatDate(item.time) }}</span>
|
||||
<span>{{ formatDate(msg.time) }}</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ item.res }}
|
||||
{{ msg.text }}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
@ -57,62 +68,113 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { useWebSocket } from '@vueuse/core'
|
||||
import { getAccessToken } from '@/utils/auth'
|
||||
import * as UserApi from '@/api/system/user'
|
||||
|
||||
defineOptions({ name: 'InfraWebSocket' })
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const sendValue = ref('')
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const server = ref(
|
||||
(import.meta.env.VITE_BASE_URL + '/websocket/message').replace('http', 'ws') +
|
||||
'?userId=' +
|
||||
userStore.getUser.id
|
||||
)
|
||||
|
||||
const state = reactive({
|
||||
recordList: [] as { id: number; time: number; res: string }[]
|
||||
})
|
||||
(import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws') + '?token=' + getAccessToken()
|
||||
) // WebSocket 服务地址
|
||||
const getIsOpen = computed(() => status.value === 'OPEN') // WebSocket 连接是否打开
|
||||
const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red')) // WebSocket 连接的展示颜色
|
||||
|
||||
/** 发起 WebSocket 连接 */
|
||||
const { status, data, send, close, open } = useWebSocket(server.value, {
|
||||
autoReconnect: false,
|
||||
heartbeat: true
|
||||
})
|
||||
|
||||
/** 监听接收到的数据 */
|
||||
const messageList = ref([] as { time: number; text: string }[]) // 消息列表
|
||||
watchEffect(() => {
|
||||
if (data.value) {
|
||||
try {
|
||||
const res = JSON.parse(data.value)
|
||||
state.recordList.push(res)
|
||||
} catch (error) {
|
||||
state.recordList.push({
|
||||
res: data.value,
|
||||
id: Math.ceil(Math.random() * 1000),
|
||||
if (!data.value) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// 1. 收到心跳
|
||||
if (data.value === 'pong') {
|
||||
// state.recordList.push({
|
||||
// text: '【心跳】',
|
||||
// time: new Date().getTime()
|
||||
// })
|
||||
return
|
||||
}
|
||||
|
||||
// 2.1 解析 type 消息类型
|
||||
const jsonMessage = JSON.parse(data.value)
|
||||
const type = jsonMessage.type
|
||||
const content = JSON.parse(jsonMessage.content)
|
||||
if (!type) {
|
||||
message.error('未知的消息类型:' + data.value)
|
||||
return
|
||||
}
|
||||
// 2.2 消息类型:demo-message-receive
|
||||
if (type === 'demo-message-receive') {
|
||||
const single = content.single
|
||||
if (single) {
|
||||
messageList.value.push({
|
||||
text: `【单发】用户编号(${content.fromUserId}):${content.text}`,
|
||||
time: new Date().getTime()
|
||||
})
|
||||
} else {
|
||||
messageList.value.push({
|
||||
text: `【群发】用户编号(${content.fromUserId}):${content.text}`,
|
||||
time: new Date().getTime()
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
// 2.3 消息类型:notice-push
|
||||
if (type === 'notice-push') {
|
||||
messageList.value.push({
|
||||
text: `【系统通知】:${content.title}`,
|
||||
time: new Date().getTime()
|
||||
})
|
||||
return
|
||||
}
|
||||
message.error('未处理消息:' + data.value)
|
||||
} catch (error) {
|
||||
message.error('处理消息发生异常:' + data.value)
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
|
||||
const getIsOpen = computed(() => status.value === 'OPEN')
|
||||
const getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red'))
|
||||
|
||||
const getList = computed(() => {
|
||||
return [...state.recordList].reverse()
|
||||
})
|
||||
|
||||
function handlerSend() {
|
||||
send(sendValue.value)
|
||||
sendValue.value = ''
|
||||
/** 发送消息 */
|
||||
const sendText = ref('') // 发送内容
|
||||
const sendUserId = ref('') // 发送人
|
||||
const handlerSend = () => {
|
||||
// 1.1 先 JSON 化 message 消息内容
|
||||
const messageContent = JSON.stringify({
|
||||
text: sendText.value,
|
||||
toUserId: sendUserId.value
|
||||
})
|
||||
// 1.2 再 JSON 化整个消息
|
||||
const jsonMessage = JSON.stringify({
|
||||
type: 'demo-message-send',
|
||||
content: messageContent
|
||||
})
|
||||
// 2. 最后发送消息
|
||||
send(jsonMessage)
|
||||
sendText.value = ''
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
/** 切换 websocket 连接状态 */
|
||||
const toggleConnectStatus = () => {
|
||||
if (getIsOpen.value) {
|
||||
close()
|
||||
} else {
|
||||
open()
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
const userList = ref<any[]>([]) // 用户列表
|
||||
onMounted(async () => {
|
||||
// 获取用户列表
|
||||
userList.value = await UserApi.getSimpleUserList()
|
||||
})
|
||||
</script>
|
||||
|
@ -8,14 +8,7 @@
|
||||
v-loading="formLoading"
|
||||
>
|
||||
<el-form-item label="商品" prop="spuId">
|
||||
<div @click="handleSelectSpu" class="h-60px w-60px">
|
||||
<div v-if="spuData && spuData.picUrl">
|
||||
<el-image :src="spuData.picUrl" />
|
||||
</div>
|
||||
<div v-else class="select-box">
|
||||
<Icon icon="ep:plus" />
|
||||
</div>
|
||||
</div>
|
||||
<SpuShowcase v-model="formData.spuId" :limit="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="商品规格" prop="skuId" v-if="formData.spuId">
|
||||
<div @click="handleSelectSku" class="h-60px w-60px">
|
||||
@ -51,12 +44,11 @@
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
<SpuTableSelect ref="spuTableSelectRef" @change="handleSpuChange" />
|
||||
<SkuTableSelect ref="skuTableSelectRef" @change="handleSkuChange" :spu-id="spuData.id" />
|
||||
<SkuTableSelect ref="skuTableSelectRef" @change="handleSkuChange" :spu-id="formData.spuId" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as CommentApi from '@/api/mall/product/comment'
|
||||
import SpuTableSelect from '@/views/mall/product/spu/components/SpuTableSelect.vue'
|
||||
import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue'
|
||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||
import SkuTableSelect from '@/views/mall/product/spu/components/SkuTableSelect.vue'
|
||||
|
||||
@ -72,8 +64,7 @@ const formData = ref({
|
||||
userId: undefined,
|
||||
userNickname: undefined,
|
||||
userAvatar: undefined,
|
||||
spuId: undefined,
|
||||
spuName: undefined,
|
||||
spuId: 0,
|
||||
skuId: undefined,
|
||||
descriptionScores: 5,
|
||||
benefitScores: 5,
|
||||
@ -90,7 +81,6 @@ const formRules = reactive({
|
||||
benefitScores: [{ required: true, message: '服务星级不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const spuData = ref<ProductSpuApi.Spu>({})
|
||||
const skuData = ref({
|
||||
id: -1,
|
||||
name: '',
|
||||
@ -149,8 +139,7 @@ const resetForm = () => {
|
||||
userId: undefined,
|
||||
userNickname: undefined,
|
||||
userAvatar: undefined,
|
||||
spuId: undefined,
|
||||
spuName: undefined,
|
||||
spuId: 0,
|
||||
skuId: undefined,
|
||||
descriptionScores: 5,
|
||||
benefitScores: 5,
|
||||
@ -160,16 +149,6 @@ const resetForm = () => {
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
|
||||
/** SPU 表格选择 */
|
||||
const spuTableSelectRef = ref()
|
||||
const handleSelectSpu = () => {
|
||||
spuTableSelectRef.value.open()
|
||||
}
|
||||
const handleSpuChange = (spu: ProductSpuApi.Spu) => {
|
||||
spuData.value = spu
|
||||
formData.value.spuId = spu.id
|
||||
}
|
||||
|
||||
/** SKU 表格选择 */
|
||||
const skuTableSelectRef = ref()
|
||||
const handleSelectSku = () => {
|
||||
|
@ -59,7 +59,7 @@
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="false">
|
||||
<el-table-column label="评论编号" align="center" prop="id" min-width="50" />
|
||||
<el-table-column label="评论编号" align="center" prop="id" min-width="80" />
|
||||
<el-table-column label="商品信息" align="center" min-width="400">
|
||||
<template #default="scope">
|
||||
<div class="row flex items-center gap-x-4px">
|
||||
|
@ -13,54 +13,64 @@
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-tooltip content="选择商品">
|
||||
<div
|
||||
v-show="!disabled"
|
||||
v-if="!limit || limit <= productSpus.length"
|
||||
class="select-box"
|
||||
@click="openSpuTableSelect"
|
||||
>
|
||||
<el-tooltip content="选择商品" v-if="canAdd">
|
||||
<div class="select-box" @click="openSpuTableSelect">
|
||||
<Icon icon="ep:plus" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<!-- 商品选择对话框(表格形式) -->
|
||||
<SpuTableSelect ref="spuTableSelectRef" multiple @change="handleSpuSelected" />
|
||||
<SpuTableSelect ref="spuTableSelectRef" :multiple="limit != 1" @change="handleSpuSelected" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||
import SpuTableSelect from '@/views/mall/product/spu/components/SpuTableSelect.vue'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { array } from 'vue-types'
|
||||
import { oneOfType } from 'vue-types'
|
||||
import { isArray } from '@/utils/is'
|
||||
|
||||
// 商品橱窗,一般用于与商品建立关系时使用
|
||||
// 提供功能:展示商品列表、添加商品、移除商品
|
||||
defineOptions({ name: 'SpuShowcase' })
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: array<number>().def([]).isRequired,
|
||||
modelValue: oneOfType<number | Array<number>>([Number, Array]).isRequired,
|
||||
// 限制数量:默认不限制
|
||||
limit: propTypes.number.def(0),
|
||||
limit: propTypes.number.def(Number.MAX_VALUE),
|
||||
disabled: propTypes.bool.def(false)
|
||||
})
|
||||
|
||||
// 计算是否可以添加
|
||||
const canAdd = computed(() => {
|
||||
// 情况一:禁用时不可以添加
|
||||
if (props.disabled) return false
|
||||
// 情况二:未指定限制数量时,可以添加
|
||||
if (!props.limit) return true
|
||||
// 情况三:检查已添加数量是否小于限制数量
|
||||
return productSpus.value.length < props.limit
|
||||
})
|
||||
|
||||
// 商品列表
|
||||
const productSpus = ref<ProductSpuApi.Spu[]>([])
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async () => {
|
||||
if (props.modelValue.length === 0) {
|
||||
const ids = isArray(props.modelValue)
|
||||
? // 情况一:多选
|
||||
props.modelValue
|
||||
: // 情况二:单选
|
||||
props.modelValue
|
||||
? [props.modelValue]
|
||||
: []
|
||||
// 不需要返显
|
||||
if (ids.length === 0) {
|
||||
productSpus.value = []
|
||||
return
|
||||
}
|
||||
// 只有商品发生变化之后,才去查询商品
|
||||
if (
|
||||
productSpus.value.length === 0 ||
|
||||
productSpus.value.some((spu) => !props.modelValue.includes(spu.id))
|
||||
) {
|
||||
debugger
|
||||
productSpus.value = await ProductSpuApi.getSpuDetailList(props.modelValue)
|
||||
if (productSpus.value.length === 0 || productSpus.value.some((spu) => !ids.includes(spu.id!))) {
|
||||
productSpus.value = await ProductSpuApi.getSpuDetailList(ids)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
@ -77,8 +87,8 @@ const openSpuTableSelect = () => {
|
||||
* 选择商品后触发
|
||||
* @param spus 选中的商品列表
|
||||
*/
|
||||
const handleSpuSelected = (spus: ProductSpuApi.Spu[]) => {
|
||||
productSpus.value = spus
|
||||
const handleSpuSelected = (spus: ProductSpuApi.Spu | ProductSpuApi.Spu[]) => {
|
||||
productSpus.value = isArray(spus) ? spus : [spus]
|
||||
emitSpuChange()
|
||||
}
|
||||
|
||||
@ -92,11 +102,17 @@ const handleRemoveSpu = (index: number) => {
|
||||
}
|
||||
const emit = defineEmits(['update:modelValue', 'change'])
|
||||
const emitSpuChange = () => {
|
||||
emit(
|
||||
'update:modelValue',
|
||||
productSpus.value.map((spu) => spu.id)
|
||||
)
|
||||
emit('change', productSpus.value)
|
||||
if (props.limit === 1) {
|
||||
const spu = productSpus.value.length > 0 ? productSpus.value[0] : null
|
||||
emit('update:modelValue', spu?.id || 0)
|
||||
emit('change', spu)
|
||||
} else {
|
||||
emit(
|
||||
'update:modelValue',
|
||||
productSpus.value.map((spu) => spu.id)
|
||||
)
|
||||
emit('change', productSpus.value)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -1,8 +1,14 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" :appendToBody="true" title="选择商品" width="70%">
|
||||
<ContentWrap>
|
||||
<el-row :gutter="20" class="mb-10px">
|
||||
<el-col :span="6">
|
||||
<el-form
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
:model="queryParams"
|
||||
class="-mb-15px"
|
||||
label-width="68px"
|
||||
>
|
||||
<el-form-item label="商品名称" prop="name">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
class="!w-240px"
|
||||
@ -10,19 +16,19 @@
|
||||
placeholder="请输入商品名称"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
</el-form-item>
|
||||
<el-form-item label="商品分类" prop="categoryId">
|
||||
<el-tree-select
|
||||
v-model="queryParams.categoryId"
|
||||
:data="categoryTreeList"
|
||||
:props="defaultProps"
|
||||
check-strictly
|
||||
class="w-1/1"
|
||||
class="!w-240px"
|
||||
node-key="id"
|
||||
placeholder="请选择商品分类"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间" prop="createTime">
|
||||
<el-date-picker
|
||||
v-model="queryParams.createTime"
|
||||
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
|
||||
@ -32,8 +38,8 @@
|
||||
type="daterange"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery">
|
||||
<Icon class="mr-5px" icon="ep:search" />
|
||||
搜索
|
||||
@ -42,30 +48,32 @@
|
||||
<Icon class="mr-5px" icon="ep:refresh" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table v-loading="loading" :data="list" show-overflow-tooltip>
|
||||
<!-- 多选模式 -->
|
||||
<el-table-column key="2" type="selection" width="55" v-if="multiple">
|
||||
<!-- 1. 多选模式(不能使用type="selection",Element会忽略Header插槽) -->
|
||||
<el-table-column width="55" v-if="multiple">
|
||||
<template #header>
|
||||
<el-checkbox
|
||||
:value="allChecked && checkedPageNos.indexOf(queryParams.pageNo) > -1"
|
||||
v-model="isCheckAll"
|
||||
:indeterminate="isIndeterminate"
|
||||
@change="handleCheckAll"
|
||||
/>
|
||||
</template>
|
||||
<template #default="{ row }">
|
||||
<el-checkbox
|
||||
:value="checkedSpuIds.indexOf(row.id) > -1"
|
||||
@change="(checked: boolean) => handleCheckOne(checked, row)"
|
||||
v-model="checkedStatus[row.id]"
|
||||
@change="(checked: boolean) => handleCheckOne(checked, row, true)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 单选模式 -->
|
||||
<!-- 2. 单选模式 -->
|
||||
<el-table-column label="#" width="55" v-else>
|
||||
<template #default="{ row }">
|
||||
<el-radio :label="row.id" v-model="selectedSpuId" @change="handleSingleSelected(row)"
|
||||
> </el-radio
|
||||
>
|
||||
<el-radio :label="row.id" v-model="selectedSpuId" @change="handleSingleSelected(row)">
|
||||
<!-- 空格不能省略,是为了让单选框不显示label,如果不指定label不会有选中的效果 -->
|
||||
|
||||
</el-radio>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column key="id" align="center" label="商品编号" prop="id" min-width="60" />
|
||||
@ -102,54 +110,71 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElTable } from 'element-plus'
|
||||
import { defaultProps, handleTree } from '@/utils/tree'
|
||||
|
||||
import * as ProductCategoryApi from '@/api/mall/product/category'
|
||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { CHANGE_EVENT } from 'element-plus'
|
||||
|
||||
type Spu = Required<ProductSpuApi.Spu>
|
||||
|
||||
/**
|
||||
* 商品表格选择对话框
|
||||
* 1. 单选模式:
|
||||
* 1.1 点击表格左侧的单选框时,结束选择,并关闭对话框
|
||||
* 1.2 再次打开时,保持选中状态
|
||||
* 2. 多选模式:
|
||||
* 2.1 点击表格左侧的多选框时,记录选中的商品
|
||||
* 2.2 切换分页时,保持商品的选中的状态
|
||||
* 2.3 点击右下角的确定按钮时,结束选择,关闭对话框
|
||||
* 2.4 再次打开时,保持选中状态
|
||||
*/
|
||||
defineOptions({ name: 'SpuTableSelect' })
|
||||
|
||||
const props = defineProps({
|
||||
// 多选
|
||||
defineProps({
|
||||
// 多选模式
|
||||
multiple: propTypes.bool.def(false)
|
||||
})
|
||||
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref<Spu[]>([]) // 列表的数据
|
||||
const loading = ref(false) // 列表的加载中
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
// 列表的总页数
|
||||
const total = ref(0)
|
||||
// 列表的数据
|
||||
const list = ref<Spu[]>([])
|
||||
// 列表的加载中
|
||||
const loading = ref(false)
|
||||
// 弹窗的是否展示
|
||||
const dialogVisible = ref(false)
|
||||
// 查询参数
|
||||
const queryParams = ref({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
tabType: 0, // 默认获取上架的商品
|
||||
// 默认获取上架的商品
|
||||
tabType: 0,
|
||||
name: '',
|
||||
categoryId: null,
|
||||
createTime: []
|
||||
}) // 查询参数
|
||||
|
||||
const selectedSpuId = ref() // 选中的商品 spuId
|
||||
})
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = (spus?: Spu[]) => {
|
||||
if (spus && spus.length > 0) {
|
||||
// todo check-box不显示选中?
|
||||
checkedSpus.value = [...spus]
|
||||
checkedSpuIds.value = spus.map((spu) => spu.id)
|
||||
} else {
|
||||
checkedSpus.value = []
|
||||
checkedSpuIds.value = []
|
||||
const open = (spuList?: Spu[]) => {
|
||||
// 重置
|
||||
checkedSpus.value = []
|
||||
checkedStatus.value = {}
|
||||
isCheckAll.value = false
|
||||
isIndeterminate.value = false
|
||||
|
||||
// 处理已选中
|
||||
if (spuList && spuList.length > 0) {
|
||||
checkedSpus.value = [...spuList]
|
||||
checkedStatus.value = Object.fromEntries(spuList.map((spu) => [spu.id, true]))
|
||||
}
|
||||
allChecked.value = false
|
||||
checkedPageNos.value = []
|
||||
|
||||
dialogVisible.value = true
|
||||
resetQuery()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
// 提供 open 方法,用于打开弹窗
|
||||
defineExpose({ open })
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
@ -158,6 +183,12 @@ const getList = async () => {
|
||||
const data = await ProductSpuApi.getSpuPage(queryParams.value)
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
// checkbox绑定undefined会有问题,需要给一个bool值
|
||||
list.value.forEach(
|
||||
(spu) => (checkedStatus.value[spu.id] = checkedStatus.value[spu.id] || false)
|
||||
)
|
||||
// 计算全选框状态
|
||||
calculateIsCheckAll()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@ -174,7 +205,8 @@ const resetQuery = () => {
|
||||
queryParams.value = {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
tabType: 0, // 默认获取上架的商品
|
||||
// 默认获取上架的商品
|
||||
tabType: 0,
|
||||
name: '',
|
||||
categoryId: null,
|
||||
createTime: []
|
||||
@ -182,65 +214,85 @@ const resetQuery = () => {
|
||||
getList()
|
||||
}
|
||||
|
||||
const allChecked = ref(false) //是否全选
|
||||
const checkedPageNos = ref<number[]>([]) //选中的页码
|
||||
const checkedSpuIds = ref<number[]>([]) //选中的商品ID
|
||||
const checkedSpus = ref<Spu[]>([]) //选中的商品
|
||||
// 是否全选
|
||||
const isCheckAll = ref(false)
|
||||
// 全选框是否处于中间状态:不是全部选中 && 任意一个选中
|
||||
const isIndeterminate = ref(false)
|
||||
// 选中的商品
|
||||
const checkedSpus = ref<Spu[]>([])
|
||||
// 选中状态:key为商品ID,value为是否选中
|
||||
const checkedStatus = ref<Record<string, boolean>>({})
|
||||
|
||||
// 选中的商品 spuId
|
||||
const selectedSpuId = ref()
|
||||
/** 单选中时触发 */
|
||||
const handleSingleSelected = (row: Spu) => {
|
||||
emits('change', row)
|
||||
const handleSingleSelected = (spu: Spu) => {
|
||||
emits(CHANGE_EVENT, spu)
|
||||
// 关闭弹窗
|
||||
dialogVisible.value = false
|
||||
// 记住上次选择的ID
|
||||
selectedSpuId.value = row.id
|
||||
selectedSpuId.value = spu.id
|
||||
}
|
||||
|
||||
/** 多选完成 */
|
||||
const handleEmitChange = () => {
|
||||
// 关闭弹窗
|
||||
dialogVisible.value = false
|
||||
emits('change', [...checkedSpus.value])
|
||||
emits(CHANGE_EVENT, [...checkedSpus.value])
|
||||
}
|
||||
|
||||
/** 确认选择时的触发事件 */
|
||||
const emits = defineEmits<{
|
||||
(e: 'change', spu: Spu | Spu[] | any): void
|
||||
change: [spu: Spu | Spu[] | any]
|
||||
}>()
|
||||
|
||||
/** 全选 */
|
||||
/** 全选/全不选 */
|
||||
const handleCheckAll = (checked: boolean) => {
|
||||
debugger
|
||||
console.log('checkAll', checked)
|
||||
allChecked.value = checked
|
||||
const index = checkedPageNos.value.indexOf(queryParams.value.pageNo)
|
||||
checkedPageNos.value.push(queryParams.value.pageNo)
|
||||
if (index > -1) {
|
||||
checkedPageNos.value.splice(index, 1)
|
||||
}
|
||||
isCheckAll.value = checked
|
||||
isIndeterminate.value = false
|
||||
|
||||
list.value.forEach((item) => handleCheckOne(checked, item))
|
||||
list.value.forEach((spu) => handleCheckOne(checked, spu, false))
|
||||
}
|
||||
|
||||
/** 选中一行 */
|
||||
const handleCheckOne = (checked: boolean, spu: Spu) => {
|
||||
/**
|
||||
* 选中一行
|
||||
* @param checked 是否选中
|
||||
* @param spu 商品
|
||||
* @param isCalcCheckAll 是否计算全选
|
||||
*/
|
||||
const handleCheckOne = (checked: boolean, spu: Spu, isCalcCheckAll: boolean) => {
|
||||
if (checked) {
|
||||
const index = checkedSpuIds.value.indexOf(spu.id)
|
||||
if (index === -1) {
|
||||
checkedSpuIds.value.push(spu.id)
|
||||
checkedSpus.value.push(spu)
|
||||
}
|
||||
checkedSpus.value.push(spu)
|
||||
checkedStatus.value[spu.id] = true
|
||||
} else {
|
||||
const index = checkedSpuIds.value.indexOf(spu.id)
|
||||
const index = findCheckedIndex(spu)
|
||||
if (index > -1) {
|
||||
checkedSpuIds.value.splice(index, 1)
|
||||
checkedSpus.value.splice(index, 1)
|
||||
checkedStatus.value[spu.id] = false
|
||||
isCheckAll.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 计算全选框状态
|
||||
if (isCalcCheckAll) {
|
||||
calculateIsCheckAll()
|
||||
}
|
||||
}
|
||||
|
||||
const categoryList = ref() // 分类列表
|
||||
const categoryTreeList = ref() // 分类树
|
||||
// 查找商品在已选中商品列表中的索引
|
||||
const findCheckedIndex = (spu: Spu) => checkedSpus.value.findIndex((item) => item.id === spu.id)
|
||||
|
||||
// 计算全选框状态
|
||||
const calculateIsCheckAll = () => {
|
||||
isCheckAll.value = list.value.every((spu) => checkedStatus.value[spu.id])
|
||||
// 计算中间状态:不是全部选中 && 任意一个选中
|
||||
isIndeterminate.value = !isCheckAll.value && list.value.some((spu) => checkedStatus.value[spu.id])
|
||||
}
|
||||
|
||||
// 分类列表
|
||||
const categoryList = ref()
|
||||
// 分类树
|
||||
const categoryTreeList = ref()
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
await getList()
|
||||
|
@ -150,15 +150,14 @@ import {
|
||||
} from '@/views/mall/promotion/coupon/formatter'
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
import * as CouponTemplateApi from '@/api/mall/promotion/coupon/couponTemplate'
|
||||
import type { GiveCouponTemplate } from '@/api/mall/product/spu'
|
||||
|
||||
defineOptions({ name: 'CouponSelect' })
|
||||
|
||||
defineProps<{
|
||||
multipleSelection: GiveCouponTemplate[]
|
||||
multipleSelection: CouponTemplateApi.CouponTemplateVO[]
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:multipleSelection', v: GiveCouponTemplate[])
|
||||
(e: 'update:multipleSelection', v: CouponTemplateApi.CouponTemplateVO[])
|
||||
}>()
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('选择优惠卷') // 弹窗的标题
|
||||
@ -210,10 +209,7 @@ const open = async () => {
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
const handleSelectionChange = (val: CouponTemplateApi.CouponTemplateVO[]) => {
|
||||
emit(
|
||||
'update:multipleSelection',
|
||||
val.map((item) => ({ id: item.id, name: item.name }))
|
||||
)
|
||||
emit('update:multipleSelection', val)
|
||||
}
|
||||
|
||||
const submitForm = () => {
|
||||
|
@ -26,15 +26,7 @@
|
||||
label="商品"
|
||||
prop="productSpuIds"
|
||||
>
|
||||
<div class="flex flex-wrap items-center gap-1">
|
||||
<div v-for="(spu, index) in productSpus" :key="spu.id" class="select-box spu-pic">
|
||||
<el-image :src="spu.picUrl" />
|
||||
<Icon class="del-icon" icon="ep:circle-close-filled" @click="handleRemoveSpu(index)" />
|
||||
</div>
|
||||
<div class="select-box" @click="openSpuTableSelect">
|
||||
<Icon icon="ep:plus" />
|
||||
</div>
|
||||
</div>
|
||||
<SpuShowcase v-model="formData.productSpuIds" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.productScope === PromotionProductScopeEnum.CATEGORY.scope"
|
||||
@ -186,18 +178,16 @@
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
<SpuTableSelect ref="spuTableSelectRef" multiple @change="handleSpuSelected" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import * as CouponTemplateApi from '@/api/mall/promotion/coupon/couponTemplate'
|
||||
import * as ProductSpuApi from '@/api/mall/product/spu'
|
||||
import {
|
||||
CouponTemplateValidityTypeEnum,
|
||||
PromotionDiscountTypeEnum,
|
||||
PromotionProductScopeEnum
|
||||
} from '@/utils/constants'
|
||||
import SpuTableSelect from '@/views/mall/product/spu/components/SpuTableSelect.vue'
|
||||
import SpuShowcase from '@/views/mall/product/spu/components/SpuShowcase.vue'
|
||||
import ProductCategorySelect from '@/views/mall/product/category/components/ProductCategorySelect.vue'
|
||||
import { convertToInteger, formatToFraction } from '@/utils'
|
||||
|
||||
@ -251,7 +241,6 @@ const formRules = reactive({
|
||||
productCategoryIds: [{ required: true, message: '分类不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
const productSpus = ref<ProductSpuApi.Spu[]>([]) // 商品列表
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
@ -354,7 +343,6 @@ const resetForm = () => {
|
||||
productCategoryIds: []
|
||||
}
|
||||
formRef.value?.resetFields()
|
||||
productSpus.value = []
|
||||
}
|
||||
|
||||
/** 获得商品范围 */
|
||||
@ -363,8 +351,6 @@ const getProductScope = async () => {
|
||||
case PromotionProductScopeEnum.SPU.scope:
|
||||
// 设置商品编号
|
||||
formData.value.productSpuIds = formData.value.productScopeValues
|
||||
// 获得商品列表
|
||||
productSpus.value = await ProductSpuApi.getSpuDetailList(formData.value.productScopeValues)
|
||||
break
|
||||
case PromotionProductScopeEnum.CATEGORY.scope:
|
||||
await nextTick(() => {
|
||||
@ -397,47 +383,6 @@ function setProductScopeValues(data: CouponTemplateApi.CouponTemplateVO) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/** 活动商品的按钮 */
|
||||
const spuTableSelectRef = ref()
|
||||
const openSpuTableSelect = () => {
|
||||
spuTableSelectRef.value.open(productSpus.value)
|
||||
}
|
||||
|
||||
/** 选择商品后触发 */
|
||||
const handleSpuSelected = (spus: ProductSpuApi.Spu[]) => {
|
||||
productSpus.value = spus
|
||||
formData.value.productSpuIds = spus.map((spu) => spu.id) as []
|
||||
}
|
||||
|
||||
/** 选择商品后触发 */
|
||||
const handleRemoveSpu = (index: number) => {
|
||||
productSpus.value.splice(index, 1)
|
||||
formData.value.productSpuIds.splice(index, 1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.select-box {
|
||||
display: flex;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border: 1px dashed var(--el-border-color-darker);
|
||||
border-radius: 8px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.spu-pic {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.del-icon {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: -10px;
|
||||
z-index: 1;
|
||||
width: 20px !important;
|
||||
height: 20px !important;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
@ -219,6 +219,8 @@ import { TradeSummaryRespVO, TradeTrendSummaryRespVO } from '@/api/mall/statisti
|
||||
import { calculateRelativeRate, fenToYuan } from '@/utils'
|
||||
import download from '@/utils/download'
|
||||
import { CardTitle } from '@/components/Card'
|
||||
import * as DateUtil from '@/utils/formatTime'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
/** 交易统计 */
|
||||
defineOptions({ name: 'TradeStatistics' })
|
||||
@ -289,6 +291,13 @@ const lineChartOptions = reactive<EChartsOption>({
|
||||
/** 处理交易状况查询 */
|
||||
const getTradeTrendData = async () => {
|
||||
trendLoading.value = true
|
||||
// 1. 处理时间: 开始与截止在同一天的, 折线图出不来, 需要延长一天
|
||||
const times = shortcutDateRangePicker.value.times
|
||||
if (DateUtil.isSameDay(times[0], times[1])) {
|
||||
// 前天
|
||||
times[0] = DateUtil.formatDate(dayjs(times[0]).subtract(1, 'd'))
|
||||
}
|
||||
// 查询数据
|
||||
await Promise.all([getTradeTrendSummary(), getTradeStatisticsList()])
|
||||
trendLoading.value = false
|
||||
}
|
||||
|
@ -87,6 +87,9 @@
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
<el-button link @click="handlePush(scope.row.id)" v-hasPermi="['system:notice:update']">
|
||||
推送
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@ -168,6 +171,17 @@ const handleDelete = async (id: number) => {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 推送按钮操作 */
|
||||
const handlePush = async (id: number) => {
|
||||
try {
|
||||
// 推送的二次确认
|
||||
await message.confirm('是否推送所选中通知?')
|
||||
// 发起推送
|
||||
await NoticeApi.pushNotice(id)
|
||||
message.success(t('推送成功'))
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
|
Loading…
Reference in New Issue
Block a user