Merge remote-tracking branch 'yudao/dev' into dev
This commit is contained in:
@@ -21,10 +21,13 @@ const emit = defineEmits<{
|
||||
(e: 'success'): void;
|
||||
}>();
|
||||
|
||||
// TODO @puhui999:展示貌似不太对;应该是左右,不是上下哈
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
labelWidth: 70,
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
formItemClass: 'col-span-2',
|
||||
labelWidth: 80,
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
|
||||
@@ -25,7 +25,6 @@ const avatar = computed(
|
||||
() => props.profile?.avatar || preferences.app.defaultAvatar,
|
||||
);
|
||||
|
||||
// TODO @puhui999:头像上传没跑通
|
||||
async function handelUpload({
|
||||
file,
|
||||
filename,
|
||||
@@ -37,9 +36,9 @@ async function handelUpload({
|
||||
const { httpRequest } = useUpload();
|
||||
// 将 Blob 转换为 File
|
||||
const fileObj = new File([file], filename, { type: file.type });
|
||||
const avatar = await httpRequest(fileObj);
|
||||
// 2. 更新用户头像
|
||||
await updateUserProfile({ avatar });
|
||||
const res = await httpRequest(fileObj);
|
||||
// 2. 更新用户头像(httpRequest 返回 { url: string })
|
||||
await updateUserProfile({ avatar: res.url });
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -57,8 +56,8 @@ async function handelUpload({
|
||||
</ElTooltip>
|
||||
</div>
|
||||
<div class="mt-8">
|
||||
<ElDescriptions :column="2">
|
||||
<ElDescriptionsItem>
|
||||
<ElDescriptions :column="2" border>
|
||||
<ElDescriptionsItem label="用户账号">
|
||||
<template #label>
|
||||
<div class="flex items-center">
|
||||
<IconifyIcon icon="ant-design:user-outlined" class="mr-1" />
|
||||
@@ -116,7 +115,11 @@ async function handelUpload({
|
||||
所属岗位
|
||||
</div>
|
||||
</template>
|
||||
{{ profile.posts.map((post) => post.name).join(',') }}
|
||||
{{
|
||||
profile.posts && profile.posts.length > 0
|
||||
? profile.posts.map((post) => post.name).join(',')
|
||||
: '-'
|
||||
}}
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem>
|
||||
<template #label>
|
||||
|
||||
@@ -100,7 +100,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
toolbarConfig: {
|
||||
enabled: false,
|
||||
},
|
||||
} as VxeTableGridOptions<SystemSocialUserApi.SocialUser>,
|
||||
},
|
||||
});
|
||||
|
||||
/** 解绑账号 */
|
||||
@@ -167,13 +167,12 @@ onMounted(() => {
|
||||
>
|
||||
<ElCard v-for="item in allBindList" :key="item.type" class="!mb-2">
|
||||
<div class="flex w-full items-center gap-4">
|
||||
<!-- TODO @puhui999:图片大小不太对 -->
|
||||
<ElImage
|
||||
:src="item.img"
|
||||
:width="40"
|
||||
:height="40"
|
||||
style="width: 40px; height: 40px"
|
||||
:alt="item.title"
|
||||
:preview="false"
|
||||
:preview-disabled="true"
|
||||
fit="contain"
|
||||
/>
|
||||
<div class="flex flex-1 items-center justify-between">
|
||||
<div class="flex flex-col">
|
||||
|
||||
@@ -5,7 +5,11 @@ import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
|
||||
import { h } from 'vue';
|
||||
|
||||
import { DocAlert, Page, prompt } from '@vben/common-ui';
|
||||
import { BpmProcessInstanceStatus, DICT_TYPE } from '@vben/constants';
|
||||
import {
|
||||
BpmModelFormType,
|
||||
BpmProcessInstanceStatus,
|
||||
DICT_TYPE,
|
||||
} from '@vben/constants';
|
||||
|
||||
import { ElButton, ElInput, ElMessage } from 'element-plus';
|
||||
|
||||
@@ -37,23 +41,34 @@ function handleDetail(row: BpmProcessInstanceApi.ProcessInstance) {
|
||||
}
|
||||
|
||||
/** 重新发起流程 */
|
||||
async function handleCreate(row: BpmProcessInstanceApi.ProcessInstance) {
|
||||
// 如果是【业务表单】,不支持重新发起
|
||||
async function handleCreate(row?: BpmProcessInstanceApi.ProcessInstance) {
|
||||
if (row?.id) {
|
||||
const processDefinitionDetail = await getProcessDefinition(
|
||||
row.processDefinitionId,
|
||||
);
|
||||
if (processDefinitionDetail.formType === 20) {
|
||||
ElMessage.error(
|
||||
'重新发起流程失败,原因:该流程使用业务表单,不支持重新发起',
|
||||
);
|
||||
if (processDefinitionDetail?.formType === BpmModelFormType.CUSTOM) {
|
||||
if (!processDefinitionDetail.formCustomCreatePath) {
|
||||
ElMessage.error('未配置业务表单的提交路由,无法重新发起');
|
||||
return;
|
||||
}
|
||||
await router.push({
|
||||
path: processDefinitionDetail.formCustomCreatePath,
|
||||
query: {
|
||||
id: row.businessKey,
|
||||
},
|
||||
});
|
||||
return;
|
||||
} else if (processDefinitionDetail?.formType === BpmModelFormType.NORMAL) {
|
||||
await router.push({
|
||||
name: 'BpmProcessInstanceCreate',
|
||||
query: { processInstanceId: row.id },
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 跳转发起流程界面
|
||||
await router.push({
|
||||
name: 'BpmProcessInstanceCreate',
|
||||
query: { processInstanceId: row?.id },
|
||||
query: row?.id ? { processInstanceId: row.id } : {},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,34 @@
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictLabel } from '@vben/hooks';
|
||||
|
||||
const getLegend = (extra: Record<string, any> = {}) => ({
|
||||
top: 10,
|
||||
...extra,
|
||||
});
|
||||
|
||||
const getGrid = (extra: Record<string, any> = {}) => ({
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
...extra,
|
||||
});
|
||||
|
||||
const getTooltip = (extra: Record<string, any> = {}) => ({
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
...extra,
|
||||
});
|
||||
|
||||
export function getChartOptions(activeTabName: any, res: any): any {
|
||||
switch (activeTabName) {
|
||||
// 客户转化率分析
|
||||
case 'conversionStat': {
|
||||
return {
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 40, // 让 X 轴右侧显示完整
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {},
|
||||
grid: getGrid(),
|
||||
legend: getLegend(),
|
||||
series: [
|
||||
{
|
||||
name: '客户转化率',
|
||||
@@ -40,12 +57,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
saveAsImage: { show: true, name: '客户转化率分析' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
tooltip: getTooltip(),
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '转化率(%)',
|
||||
@@ -59,14 +71,13 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
}
|
||||
case 'customerSummary': {
|
||||
return {
|
||||
grid: {
|
||||
bottom: '5%',
|
||||
containLabel: true,
|
||||
grid: getGrid({
|
||||
bottom: '8%',
|
||||
left: '5%',
|
||||
right: '5%',
|
||||
top: '5 %',
|
||||
},
|
||||
legend: {},
|
||||
top: 80,
|
||||
}),
|
||||
legend: getLegend(),
|
||||
series: [
|
||||
{
|
||||
name: '新增客户数',
|
||||
@@ -92,12 +103,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
saveAsImage: { show: true, name: '客户总量分析' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
tooltip: getTooltip(),
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
@@ -134,13 +140,8 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
};
|
||||
});
|
||||
return {
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 40, // 让 X 轴右侧显示完整
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {},
|
||||
grid: getGrid(),
|
||||
legend: getLegend(),
|
||||
series: [
|
||||
{
|
||||
name: '成交周期(天)',
|
||||
@@ -166,12 +167,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
saveAsImage: { show: true, name: '成交周期分析' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
tooltip: getTooltip(),
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
@@ -208,13 +204,8 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
};
|
||||
});
|
||||
return {
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 40, // 让 X 轴右侧显示完整
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {},
|
||||
grid: getGrid(),
|
||||
legend: getLegend(),
|
||||
series: [
|
||||
{
|
||||
name: '成交周期(天)',
|
||||
@@ -240,12 +231,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
saveAsImage: { show: true, name: '成交周期分析' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
tooltip: getTooltip(),
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
@@ -277,13 +263,8 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
const customerDealCycleByDate = res.customerDealCycleByDate;
|
||||
const customerDealCycleByUser = res.customerDealCycleByUser;
|
||||
return {
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 40, // 让 X 轴右侧显示完整
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {},
|
||||
grid: getGrid(),
|
||||
legend: getLegend(),
|
||||
series: [
|
||||
{
|
||||
name: '成交周期(天)',
|
||||
@@ -309,12 +290,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
saveAsImage: { show: true, name: '成交周期分析' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
tooltip: getTooltip(),
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
@@ -342,15 +318,13 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
},
|
||||
};
|
||||
}
|
||||
// 客户跟进次数分析
|
||||
case 'followUpSummary': {
|
||||
return {
|
||||
grid: {
|
||||
left: 20,
|
||||
grid: getGrid({
|
||||
right: 30, // 让 X 轴右侧显示完整
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {},
|
||||
}),
|
||||
legend: getLegend(),
|
||||
series: [
|
||||
{
|
||||
name: '跟进客户数',
|
||||
@@ -376,12 +350,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
saveAsImage: { show: true, name: '客户跟进次数分析' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
tooltip: getTooltip(),
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
@@ -412,20 +381,21 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
},
|
||||
};
|
||||
}
|
||||
// 客户跟进方式分析
|
||||
case 'followUpType': {
|
||||
return {
|
||||
title: {
|
||||
text: '客户跟进方式分析',
|
||||
left: 'center',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
legend: getLegend({
|
||||
left: 'left',
|
||||
},
|
||||
tooltip: {
|
||||
}),
|
||||
tooltip: getTooltip({
|
||||
trigger: 'item',
|
||||
axisPointer: undefined,
|
||||
formatter: '{b} : {c}% ',
|
||||
},
|
||||
}),
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: { show: true, name: '客户跟进方式分析' }, // 保存为图片
|
||||
@@ -458,13 +428,8 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
}
|
||||
case 'poolSummary': {
|
||||
return {
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 40, // 让 X 轴右侧显示完整
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {},
|
||||
grid: getGrid(),
|
||||
legend: getLegend(),
|
||||
series: [
|
||||
{
|
||||
name: '进入公海客户数',
|
||||
@@ -490,12 +455,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
saveAsImage: { show: true, name: '公海客户分析' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
tooltip: getTooltip(),
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
import { erpCalculatePercentage } from '@vben/utils';
|
||||
|
||||
const getLegend = (extra: Record<string, any> = {}) => ({
|
||||
top: 10,
|
||||
...extra,
|
||||
});
|
||||
|
||||
const getGrid = (extra: Record<string, any> = {}) => ({
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
...extra,
|
||||
});
|
||||
|
||||
const getTooltip = (extra: Record<string, any> = {}) => ({
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
...extra,
|
||||
});
|
||||
|
||||
export function getChartOptions(
|
||||
activeTabName: any,
|
||||
active: boolean,
|
||||
@@ -9,26 +30,19 @@ export function getChartOptions(
|
||||
case 'businessInversionRateSummary': {
|
||||
return {
|
||||
color: ['#6ca2ff', '#6ac9d7', '#ff7474'],
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
// 坐标轴指示器,坐标轴触发有效
|
||||
type: 'shadow', // 默认为直线,可选为:'line' | 'shadow'
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
tooltip: getTooltip(),
|
||||
legend: getLegend({
|
||||
data: ['赢单转化率', '商机总数', '赢单商机数'],
|
||||
bottom: '0px',
|
||||
itemWidth: 14,
|
||||
},
|
||||
grid: {
|
||||
}),
|
||||
grid: getGrid({
|
||||
top: '40px',
|
||||
left: '40px',
|
||||
right: '40px',
|
||||
bottom: '40px',
|
||||
containLabel: true,
|
||||
borderColor: '#fff',
|
||||
},
|
||||
}),
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
@@ -117,13 +131,11 @@ export function getChartOptions(
|
||||
}
|
||||
case 'businessSummary': {
|
||||
return {
|
||||
grid: {
|
||||
grid: getGrid({
|
||||
left: 30,
|
||||
right: 30, // 让 X 轴右侧显示完整
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {},
|
||||
}),
|
||||
legend: getLegend(),
|
||||
series: [
|
||||
{
|
||||
name: '新增商机数量',
|
||||
@@ -149,12 +161,7 @@ export function getChartOptions(
|
||||
saveAsImage: { show: true, name: '新增商机分析' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
tooltip: getTooltip(),
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
@@ -211,10 +218,11 @@ export function getChartOptions(
|
||||
title: {
|
||||
text: '销售漏斗',
|
||||
},
|
||||
tooltip: {
|
||||
tooltip: getTooltip({
|
||||
trigger: 'item',
|
||||
axisPointer: undefined,
|
||||
formatter: '{a} <br/>{b}',
|
||||
},
|
||||
}),
|
||||
toolbox: {
|
||||
feature: {
|
||||
dataView: { readOnly: false },
|
||||
@@ -222,9 +230,9 @@ export function getChartOptions(
|
||||
saveAsImage: {},
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
legend: getLegend({
|
||||
data: ['客户', '商机', '赢单'],
|
||||
},
|
||||
}),
|
||||
series: [
|
||||
{
|
||||
name: '销售漏斗',
|
||||
|
||||
@@ -1,14 +1,30 @@
|
||||
const getLegend = (extra: Record<string, any> = {}) => ({
|
||||
top: 10,
|
||||
...extra,
|
||||
});
|
||||
|
||||
const getGrid = (extra: Record<string, any> = {}) => ({
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
...extra,
|
||||
});
|
||||
|
||||
const getTooltip = (extra: Record<string, any> = {}) => ({
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
...extra,
|
||||
});
|
||||
|
||||
export function getChartOptions(activeTabName: any, res: any): any {
|
||||
switch (activeTabName) {
|
||||
case 'ContractCountPerformance': {
|
||||
return {
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {},
|
||||
grid: getGrid(),
|
||||
legend: getLegend(),
|
||||
series: [
|
||||
{
|
||||
name: '当月合同数量(个)',
|
||||
@@ -65,12 +81,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
saveAsImage: { show: true, name: '客户总量分析' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
tooltip: getTooltip(),
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
@@ -131,13 +142,8 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
}
|
||||
case 'ContractPricePerformance': {
|
||||
return {
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {},
|
||||
grid: getGrid(),
|
||||
legend: getLegend(),
|
||||
series: [
|
||||
{
|
||||
name: '当月合同金额(元)',
|
||||
@@ -260,13 +266,8 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
}
|
||||
case 'ReceivablePricePerformance': {
|
||||
return {
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {},
|
||||
grid: getGrid(),
|
||||
legend: getLegend(),
|
||||
series: [
|
||||
{
|
||||
name: '当月回款金额(元)',
|
||||
|
||||
@@ -13,6 +13,71 @@ function areaReplace(areaName: string) {
|
||||
.replace('省', '');
|
||||
}
|
||||
|
||||
const getPieTooltip = (extra: Record<string, any> = {}) => ({
|
||||
trigger: 'item',
|
||||
...extra,
|
||||
});
|
||||
|
||||
const getPieLegend = (extra: Record<string, any> = {}) => ({
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
...extra,
|
||||
});
|
||||
|
||||
const getPieSeries = (name: string, data: any[]) => ({
|
||||
name,
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center',
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
data,
|
||||
});
|
||||
|
||||
const getPiePanel = ({
|
||||
data,
|
||||
legendExtra,
|
||||
seriesName,
|
||||
title,
|
||||
tooltipExtra,
|
||||
}: {
|
||||
data: any[];
|
||||
legendExtra?: Record<string, any>;
|
||||
seriesName: string;
|
||||
title: string;
|
||||
tooltipExtra?: Record<string, any>;
|
||||
}) => ({
|
||||
title: {
|
||||
text: title,
|
||||
left: 'center',
|
||||
},
|
||||
tooltip: getPieTooltip(tooltipExtra),
|
||||
legend: getPieLegend(legendExtra),
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: { show: true, name: title },
|
||||
},
|
||||
},
|
||||
series: [getPieSeries(seriesName, data)],
|
||||
});
|
||||
|
||||
export function getChartOptions(activeTabName: any, res: any): any {
|
||||
switch (activeTabName) {
|
||||
case 'area': {
|
||||
@@ -111,326 +176,62 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
}
|
||||
case 'industry': {
|
||||
return {
|
||||
left: {
|
||||
title: {
|
||||
text: '全部客户',
|
||||
left: 'center',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: { show: true, name: '全部客户' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '全部客户',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center',
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
data: res.map((r: any) => {
|
||||
return {
|
||||
name: getDictLabel(
|
||||
DICT_TYPE.CRM_CUSTOMER_INDUSTRY,
|
||||
r.industryId,
|
||||
),
|
||||
value: r.customerCount,
|
||||
};
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
right: {
|
||||
title: {
|
||||
text: '成交客户',
|
||||
left: 'center',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: { show: true, name: '成交客户' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '成交客户',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center',
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
data: res.map((r: any) => {
|
||||
return {
|
||||
name: getDictLabel(
|
||||
DICT_TYPE.CRM_CUSTOMER_INDUSTRY,
|
||||
r.industryId,
|
||||
),
|
||||
value: r.dealCount,
|
||||
};
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
left: getPiePanel({
|
||||
title: '全部客户',
|
||||
seriesName: '全部客户',
|
||||
data: res.map((r: any) => ({
|
||||
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_INDUSTRY, r.industryId),
|
||||
value: r.customerCount,
|
||||
})),
|
||||
}),
|
||||
right: getPiePanel({
|
||||
title: '成交客户',
|
||||
seriesName: '成交客户',
|
||||
data: res.map((r: any) => ({
|
||||
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_INDUSTRY, r.industryId),
|
||||
value: r.dealCount,
|
||||
})),
|
||||
}),
|
||||
};
|
||||
}
|
||||
case 'level': {
|
||||
return {
|
||||
left: {
|
||||
title: {
|
||||
text: '全部客户',
|
||||
left: 'center',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: { show: true, name: '全部客户' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '全部客户',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center',
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
data: res.map((r: any) => {
|
||||
return {
|
||||
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_LEVEL, r.level),
|
||||
value: r.customerCount,
|
||||
};
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
right: {
|
||||
title: {
|
||||
text: '成交客户',
|
||||
left: 'center',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: { show: true, name: '成交客户' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '成交客户',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center',
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
data: res.map((r: any) => {
|
||||
return {
|
||||
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_LEVEL, r.level),
|
||||
value: r.dealCount,
|
||||
};
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
left: getPiePanel({
|
||||
title: '全部客户',
|
||||
seriesName: '全部客户',
|
||||
data: res.map((r: any) => ({
|
||||
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_LEVEL, r.level),
|
||||
value: r.customerCount,
|
||||
})),
|
||||
}),
|
||||
right: getPiePanel({
|
||||
title: '成交客户',
|
||||
seriesName: '成交客户',
|
||||
data: res.map((r: any) => ({
|
||||
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_LEVEL, r.level),
|
||||
value: r.dealCount,
|
||||
})),
|
||||
}),
|
||||
};
|
||||
}
|
||||
case 'source': {
|
||||
return {
|
||||
left: {
|
||||
title: {
|
||||
text: '全部客户',
|
||||
left: 'center',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: { show: true, name: '全部客户' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '全部客户',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center',
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
data: res.map((r: any) => {
|
||||
return {
|
||||
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_SOURCE, r.source),
|
||||
value: r.customerCount,
|
||||
};
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
right: {
|
||||
title: {
|
||||
text: '成交客户',
|
||||
left: 'center',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
saveAsImage: { show: true, name: '成交客户' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '成交客户',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center',
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
data: res.map((r: any) => {
|
||||
return {
|
||||
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_SOURCE, r.source),
|
||||
value: r.dealCount,
|
||||
};
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
left: getPiePanel({
|
||||
title: '全部客户',
|
||||
seriesName: '全部客户',
|
||||
data: res.map((r: any) => ({
|
||||
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_SOURCE, r.source),
|
||||
value: r.customerCount,
|
||||
})),
|
||||
}),
|
||||
right: getPiePanel({
|
||||
title: '成交客户',
|
||||
seriesName: '成交客户',
|
||||
data: res.map((r: any) => ({
|
||||
name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_SOURCE, r.source),
|
||||
value: r.dealCount,
|
||||
})),
|
||||
}),
|
||||
};
|
||||
}
|
||||
default: {
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
const getLegend = (extra: Record<string, any> = {}) => ({
|
||||
top: 10,
|
||||
...extra,
|
||||
});
|
||||
|
||||
const getGrid = (extra: Record<string, any> = {}) => ({
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
...extra,
|
||||
});
|
||||
|
||||
const getTooltip = () => ({
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
});
|
||||
|
||||
export function getChartOptions(activeTabName: any, res: any): any {
|
||||
switch (activeTabName) {
|
||||
case 'contactCountRank': {
|
||||
@@ -8,15 +28,8 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
dimensions: ['nickname', 'count'],
|
||||
source: cloneDeep(res).toReversed(),
|
||||
},
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {
|
||||
top: 50,
|
||||
},
|
||||
grid: getGrid(),
|
||||
legend: getLegend(),
|
||||
series: [
|
||||
{
|
||||
name: '新增联系人数排行',
|
||||
@@ -34,12 +47,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
saveAsImage: { show: true, name: '新增联系人数排行' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
tooltip: getTooltip(),
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
name: '新增联系人数(个)',
|
||||
@@ -56,15 +64,8 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
dimensions: ['nickname', 'count'],
|
||||
source: cloneDeep(res).toReversed(),
|
||||
},
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {
|
||||
top: 50,
|
||||
},
|
||||
grid: getGrid(),
|
||||
legend: getLegend(),
|
||||
series: [
|
||||
{
|
||||
name: '签约合同排行',
|
||||
@@ -82,12 +83,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
saveAsImage: { show: true, name: '签约合同排行' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
tooltip: getTooltip(),
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
name: '签约合同数(个)',
|
||||
@@ -104,15 +100,8 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
dimensions: ['nickname', 'count'],
|
||||
source: cloneDeep(res).toReversed(),
|
||||
},
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {
|
||||
top: 50,
|
||||
},
|
||||
grid: getGrid(),
|
||||
legend: getLegend(),
|
||||
series: [
|
||||
{
|
||||
name: '合同金额排行',
|
||||
@@ -130,12 +119,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
saveAsImage: { show: true, name: '合同金额排行' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
tooltip: getTooltip(),
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
name: '合同金额(元)',
|
||||
@@ -152,15 +136,8 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
dimensions: ['nickname', 'count'],
|
||||
source: cloneDeep(res).toReversed(),
|
||||
},
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
containLabel: true,
|
||||
},
|
||||
legend: {
|
||||
top: 50,
|
||||
},
|
||||
grid: getGrid(),
|
||||
legend: getLegend(),
|
||||
series: [
|
||||
{
|
||||
name: '新增客户数排行',
|
||||
@@ -178,12 +155,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
saveAsImage: { show: true, name: '新增客户数排行' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
tooltip: getTooltip(),
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
name: '新增客户数(个)',
|
||||
@@ -226,12 +198,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
saveAsImage: { show: true, name: '跟进次数排行' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
tooltip: getTooltip(),
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
name: '跟进次数(次)',
|
||||
@@ -274,12 +241,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
saveAsImage: { show: true, name: '跟进客户数排行' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
tooltip: getTooltip(),
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
name: '跟进客户数(个)',
|
||||
@@ -322,12 +284,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
saveAsImage: { show: true, name: '产品销量排行' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
tooltip: getTooltip(),
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
name: '产品销量',
|
||||
@@ -370,12 +327,7 @@ export function getChartOptions(activeTabName: any, res: any): any {
|
||||
saveAsImage: { show: true, name: '回款金额排行' }, // 保存为图片
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
tooltip: getTooltip(),
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
name: '回款金额(元)',
|
||||
|
||||
@@ -490,16 +490,25 @@ defineExpose({
|
||||
@checkbox-all="handleSelectionChange"
|
||||
>
|
||||
<VxeColumn v-if="isComponent" type="checkbox" width="45" fixed="left" />
|
||||
<!-- TODO @puhui999:这里的宽度貌似有点问题,图片会寄出来; -->
|
||||
<VxeColumn align="center" title="图片" max-width="140" fixed="left">
|
||||
<VxeColumn
|
||||
align="center"
|
||||
title="图片"
|
||||
width="80"
|
||||
min-width="80"
|
||||
fixed="left"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<ElImage
|
||||
v-if="row.picUrl"
|
||||
:src="row.picUrl"
|
||||
class="h-[50px] w-[50px] cursor-pointer"
|
||||
:preview-src-list="[row.picUrl]"
|
||||
fit="cover"
|
||||
/>
|
||||
<div class="flex items-center justify-center overflow-hidden">
|
||||
<ElImage
|
||||
v-if="row.picUrl"
|
||||
:src="row.picUrl"
|
||||
class="h-[50px] w-[50px] cursor-pointer"
|
||||
:preview-src-list="[row.picUrl]"
|
||||
:preview-teleported="true"
|
||||
:z-index="3000"
|
||||
fit="cover"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</VxeColumn>
|
||||
<template v-if="formData?.specType && !isBatch">
|
||||
@@ -583,15 +592,24 @@ defineExpose({
|
||||
}"
|
||||
>
|
||||
<VxeColumn v-if="isComponent" type="checkbox" width="45" fixed="left" />
|
||||
<!-- TODO @puhui999:这里的宽度貌似有点问题,图片会寄出来; -->
|
||||
<VxeColumn align="center" title="图片" max-width="140" fixed="left">
|
||||
<VxeColumn
|
||||
align="center"
|
||||
title="图片"
|
||||
width="80"
|
||||
min-width="80"
|
||||
fixed="left"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<ElImage
|
||||
:src="row.picUrl"
|
||||
class="h-[60px] w-[60px] cursor-pointer"
|
||||
:preview-src-list="[row.picUrl]"
|
||||
fit="cover"
|
||||
/>
|
||||
<div class="flex items-center justify-center overflow-hidden">
|
||||
<ElImage
|
||||
:src="row.picUrl"
|
||||
class="h-[60px] w-[60px] cursor-pointer"
|
||||
:preview-src-list="[row.picUrl]"
|
||||
:preview-teleported="true"
|
||||
:z-index="3000"
|
||||
fit="cover"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</VxeColumn>
|
||||
<template v-if="formData?.specType">
|
||||
|
||||
@@ -130,12 +130,13 @@ watch(
|
||||
<VxeColumn field="id" align="center" title="商品编号" min-width="30" />
|
||||
<VxeColumn title="商品图" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<!-- TODO @puhui999:它的 preview 貌似展示有点奇怪,不像 antd 是全屏的。。。 -->
|
||||
<ElImage
|
||||
v-if="row.picUrl"
|
||||
:src="row.picUrl"
|
||||
class="h-[30px] w-[30px] cursor-pointer"
|
||||
:preview-src-list="[row.picUrl]"
|
||||
:preview-teleported="true"
|
||||
:z-index="3000"
|
||||
fit="cover"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -320,7 +320,6 @@ onMounted(async () => {
|
||||
<ElCard class="h-full w-full" v-loading="formLoading">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<!-- TODO @puhui999:这里有告警,需要修复下。 -->
|
||||
<ElTabs v-model="activeTabName" @tab-change="handleTabChange">
|
||||
<ElTabPane label="基础设置" name="info" />
|
||||
<ElTabPane label="价格库存" name="sku" />
|
||||
|
||||
@@ -75,7 +75,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
fieldName: 'spuIds',
|
||||
label: '活动商品',
|
||||
component: 'Input',
|
||||
rules: 'required',
|
||||
formItemClass: 'col-span-2',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,30 +1,52 @@
|
||||
<script lang="ts" setup>
|
||||
import type { MallSpuApi } from '#/api/mall/product/spu';
|
||||
import type { MallDiscountActivityApi } from '#/api/mall/promotion/discount/discountActivity';
|
||||
import type {
|
||||
PropertyAndValues,
|
||||
RuleConfig,
|
||||
SpuProperty,
|
||||
} from '#/views/mall/product/spu/components';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
|
||||
import { useVbenForm, useVbenModal } from '@vben/common-ui';
|
||||
import {
|
||||
convertToInteger,
|
||||
erpCalculatePercentage,
|
||||
formatToFraction,
|
||||
yuanToFen,
|
||||
} from '@vben/utils';
|
||||
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { ElButton, ElInputNumber, ElMessage } from 'element-plus';
|
||||
|
||||
import { VxeColumn } from '#/adapter/vxe-table';
|
||||
import { getSpuDetailList } from '#/api/mall/product/spu';
|
||||
import {
|
||||
createDiscountActivity,
|
||||
getDiscountActivity,
|
||||
updateDiscountActivity,
|
||||
} from '#/api/mall/promotion/discount/discountActivity';
|
||||
import { $t } from '#/locales';
|
||||
import { SpuShowcase } from '#/views/mall/product/spu/components';
|
||||
import {
|
||||
getPropertyList,
|
||||
SpuAndSkuList,
|
||||
SpuSkuSelect,
|
||||
} from '#/views/mall/product/spu/components';
|
||||
|
||||
import { useFormSchema } from '../data';
|
||||
|
||||
defineOptions({ name: 'DiscountActivityForm' });
|
||||
|
||||
/** 折扣类型枚举 */
|
||||
const PromotionDiscountTypeEnum = {
|
||||
PRICE: { type: 1 }, // 满减
|
||||
PERCENT: { type: 2 }, // 折扣
|
||||
};
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const formData = ref<
|
||||
Partial<MallDiscountActivityApi.DiscountActivity> & {
|
||||
spuIds?: number[];
|
||||
}
|
||||
>({});
|
||||
|
||||
// ================= 表单相关 =================
|
||||
const formData = ref<Partial<MallDiscountActivityApi.DiscountActivity>>({});
|
||||
const getTitle = computed(() => {
|
||||
return formData.value?.id
|
||||
? $t('ui.actionTitle.edit', ['限时折扣活动'])
|
||||
@@ -44,28 +66,203 @@ const [Form, formApi] = useVbenForm({
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
// TODO @puhui999:antd 和 ele 里,修改时,商品都没展示。
|
||||
// ================= 商品选择相关 =================
|
||||
/** SKU 扩展类型 */
|
||||
interface SkuExtension extends MallSpuApi.Sku {
|
||||
productConfig: MallDiscountActivityApi.DiscountProduct;
|
||||
}
|
||||
|
||||
/** SPU 扩展类型 */
|
||||
interface SpuExtension extends MallSpuApi.Spu {
|
||||
skus?: SkuExtension[];
|
||||
}
|
||||
|
||||
const spuSelectRef = ref(); // 商品选择组件 Ref
|
||||
const spuAndSkuListRef = ref(); // SKU 列表组件 Ref
|
||||
const spuList = ref<SpuExtension[]>([]); // 选择的 SPU 列表
|
||||
const spuPropertyList = ref<SpuProperty<SpuExtension>[]>([]); // SPU 属性列表
|
||||
const spuIdList = ref<number[]>([]); // 已选择的 SPU ID 列表
|
||||
|
||||
/** SKU 校验规则配置 */
|
||||
const ruleConfig: RuleConfig[] = [
|
||||
{
|
||||
name: 'productConfig.discountPrice',
|
||||
rule: (arg) => arg > 0,
|
||||
message: '商品优惠金额不能为 0 !!!',
|
||||
},
|
||||
];
|
||||
|
||||
/** 打开商品选择弹窗 */
|
||||
function openSpuSelect() {
|
||||
spuSelectRef.value?.open();
|
||||
}
|
||||
|
||||
/** 选择商品后的回调 */
|
||||
function handleSpuSelected(spuId: number, skuIds?: number[]) {
|
||||
getSpuDetails(spuId, skuIds);
|
||||
}
|
||||
|
||||
/** 获取 SPU 详情 */
|
||||
async function getSpuDetails(
|
||||
spuId: number,
|
||||
skuIdArr?: number[],
|
||||
products?: MallDiscountActivityApi.DiscountProduct[],
|
||||
type?: string,
|
||||
) {
|
||||
// 如果已经包含该 SPU 则跳过
|
||||
if (spuIdList.value.includes(spuId)) {
|
||||
if (type !== 'load') {
|
||||
ElMessage.error('数据重复选择!');
|
||||
}
|
||||
return;
|
||||
}
|
||||
spuIdList.value.push(spuId);
|
||||
|
||||
const res = (await getSpuDetailList([spuId])) as SpuExtension[];
|
||||
if (res.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const spu = res[0]!;
|
||||
// 筛选 SKU
|
||||
const selectSkus =
|
||||
skuIdArr === undefined
|
||||
? spu.skus
|
||||
: spu.skus?.filter((sku) => skuIdArr.includes(sku.id!));
|
||||
|
||||
// 为每个 SKU 添加折扣配置
|
||||
selectSkus?.forEach((sku) => {
|
||||
let config: MallDiscountActivityApi.DiscountProduct = {
|
||||
skuId: sku.id!,
|
||||
spuId: spu.id!,
|
||||
discountType: 1,
|
||||
discountPercent: 0,
|
||||
discountPrice: 0,
|
||||
};
|
||||
|
||||
// 编辑时,使用已有的配置
|
||||
if (products !== undefined) {
|
||||
const product = products.find((item) => item.skuId === sku.id);
|
||||
if (product) {
|
||||
// 转换为元显示
|
||||
config = {
|
||||
...product,
|
||||
discountPercent: Number(formatToFraction(product.discountPercent)),
|
||||
discountPrice: Number(formatToFraction(product.discountPrice)),
|
||||
};
|
||||
}
|
||||
}
|
||||
(sku as SkuExtension).productConfig = config;
|
||||
});
|
||||
|
||||
spu.skus = selectSkus as SkuExtension[];
|
||||
spuPropertyList.value.push({
|
||||
spuId: spu.id!,
|
||||
spuDetail: spu,
|
||||
propertyList: getPropertyList(spu) as PropertyAndValues[],
|
||||
});
|
||||
spuList.value.push(spu);
|
||||
}
|
||||
|
||||
/** 删除 SPU */
|
||||
function handleDeleteSpu(spuId: number) {
|
||||
const spuIndex = spuIdList.value.indexOf(spuId);
|
||||
if (spuIndex !== -1) {
|
||||
spuIdList.value.splice(spuIndex, 1);
|
||||
}
|
||||
const propertyIndex = spuPropertyList.value.findIndex(
|
||||
(item) => item.spuId === spuId,
|
||||
);
|
||||
if (propertyIndex !== -1) {
|
||||
spuPropertyList.value.splice(propertyIndex, 1);
|
||||
}
|
||||
const listIndex = spuList.value.findIndex((item) => item.id === spuId);
|
||||
if (listIndex !== -1) {
|
||||
spuList.value.splice(listIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理 SKU 优惠金额变动 */
|
||||
function handleSkuDiscountPriceChange(row: SkuExtension) {
|
||||
if (row.productConfig.discountPrice <= 0) {
|
||||
return;
|
||||
}
|
||||
// 设置优惠类型:满减
|
||||
row.productConfig.discountType = PromotionDiscountTypeEnum.PRICE.type;
|
||||
// 计算折扣百分比
|
||||
const price = typeof row.price === 'number' ? row.price : Number(row.price);
|
||||
const percent = erpCalculatePercentage(
|
||||
price - yuanToFen(row.productConfig.discountPrice),
|
||||
price,
|
||||
);
|
||||
row.productConfig.discountPercent =
|
||||
typeof percent === 'number' ? percent : Number(percent);
|
||||
}
|
||||
|
||||
/** 处理 SKU 折扣百分比变动 */
|
||||
function handleSkuDiscountPercentChange(row: SkuExtension) {
|
||||
if (
|
||||
row.productConfig.discountPercent <= 0 ||
|
||||
row.productConfig.discountPercent >= 100
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// 设置优惠类型:折扣
|
||||
row.productConfig.discountType = PromotionDiscountTypeEnum.PERCENT.type;
|
||||
// 计算优惠金额
|
||||
const price = typeof row.price === 'number' ? row.price : Number(row.price);
|
||||
row.productConfig.discountPrice = Number(
|
||||
formatToFraction(price - price * (row.productConfig.discountPercent / 100)),
|
||||
);
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
async function resetForm() {
|
||||
spuList.value = [];
|
||||
spuPropertyList.value = [];
|
||||
spuIdList.value = [];
|
||||
formData.value = {};
|
||||
await nextTick();
|
||||
await formApi.resetForm();
|
||||
}
|
||||
|
||||
// ================= 弹窗相关 =================
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
modalApi.lock();
|
||||
// 提交表单
|
||||
const data =
|
||||
(await formApi.getValues()) as MallDiscountActivityApi.DiscountActivity;
|
||||
|
||||
// 确保必要的默认值
|
||||
if (!data.products) {
|
||||
data.products = [];
|
||||
// 校验是否选择了商品
|
||||
if (spuList.value.length === 0) {
|
||||
ElMessage.warning('请选择活动商品');
|
||||
return;
|
||||
}
|
||||
|
||||
modalApi.lock();
|
||||
try {
|
||||
// 获取折扣商品配置
|
||||
const products = structuredClone(
|
||||
spuAndSkuListRef.value?.getSkuConfigs('productConfig') || [],
|
||||
) as MallDiscountActivityApi.DiscountProduct[];
|
||||
|
||||
// 转换金额为分
|
||||
products.forEach((item) => {
|
||||
item.discountPercent = convertToInteger(item.discountPercent);
|
||||
item.discountPrice = convertToInteger(item.discountPrice);
|
||||
});
|
||||
|
||||
const data = structuredClone(
|
||||
await formApi.getValues(),
|
||||
) as MallDiscountActivityApi.DiscountActivity;
|
||||
data.products = products;
|
||||
|
||||
// 提交请求
|
||||
await (formData.value?.id
|
||||
? updateDiscountActivity(data)
|
||||
: createDiscountActivity(data));
|
||||
// 关闭并提示
|
||||
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
ElMessage.success($t('ui.actionMessage.operationSuccess'));
|
||||
@@ -75,19 +272,45 @@ const [Modal, modalApi] = useVbenModal({
|
||||
},
|
||||
async onOpenChange(isOpen: boolean) {
|
||||
if (!isOpen) {
|
||||
formData.value = {};
|
||||
await resetForm();
|
||||
return;
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
const data = modalApi.getData<MallDiscountActivityApi.DiscountActivity>();
|
||||
if (!data || !data.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
modalApi.lock();
|
||||
try {
|
||||
formData.value = await getDiscountActivity(data.id);
|
||||
// 设置到 values
|
||||
await formApi.setValues(formData.value);
|
||||
const activityData = await getDiscountActivity(data.id);
|
||||
formData.value = activityData;
|
||||
|
||||
// 加载商品详情
|
||||
if (activityData.products && activityData.products.length > 0) {
|
||||
// 按 spuId 分组
|
||||
const spuProductsMap = new Map<
|
||||
number,
|
||||
MallDiscountActivityApi.DiscountProduct[]
|
||||
>();
|
||||
for (const product of activityData.products) {
|
||||
const spuId = product.spuId;
|
||||
if (!spuProductsMap.has(spuId)) {
|
||||
spuProductsMap.set(spuId, []);
|
||||
}
|
||||
spuProductsMap.get(spuId)!.push(product);
|
||||
}
|
||||
|
||||
// 加载每个 SPU 的详情
|
||||
for (const [spuId, products] of spuProductsMap) {
|
||||
const skuIdArr = products.map((p) => p.skuId);
|
||||
await getSpuDetails(spuId, skuIdArr, products, 'load');
|
||||
}
|
||||
}
|
||||
|
||||
// 设置表单值
|
||||
await formApi.setValues(activityData);
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
@@ -96,12 +319,59 @@ const [Modal, modalApi] = useVbenModal({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-3/5" :title="getTitle">
|
||||
<Modal class="w-[70%]" :title="getTitle">
|
||||
<Form>
|
||||
<!-- 自定义插槽:商品选择 -->
|
||||
<template #spuIds>
|
||||
<SpuShowcase v-model="formData.spuIds" />
|
||||
<div class="w-full">
|
||||
<ElButton class="mb-4" @click="openSpuSelect">选择商品</ElButton>
|
||||
<SpuAndSkuList
|
||||
ref="spuAndSkuListRef"
|
||||
:deletable="true"
|
||||
:rule-config="ruleConfig"
|
||||
:spu-list="spuList"
|
||||
:spu-property-list-p="spuPropertyList"
|
||||
@delete="handleDeleteSpu"
|
||||
>
|
||||
<!-- 扩展列:限时折扣活动特有配置 -->
|
||||
<template #default>
|
||||
<VxeColumn align="center" min-width="168" title="优惠金额(元)">
|
||||
<template #default="{ row }">
|
||||
<ElInputNumber
|
||||
v-model="row.productConfig.discountPrice"
|
||||
:max="Number(formatToFraction(row.price))"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="0.1"
|
||||
class="w-full"
|
||||
@change="handleSkuDiscountPriceChange(row)"
|
||||
/>
|
||||
</template>
|
||||
</VxeColumn>
|
||||
<VxeColumn align="center" min-width="168" title="折扣百分比(%)">
|
||||
<template #default="{ row }">
|
||||
<ElInputNumber
|
||||
v-model="row.productConfig.discountPercent"
|
||||
:max="100"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="0.1"
|
||||
class="w-full"
|
||||
@change="handleSkuDiscountPercentChange(row)"
|
||||
/>
|
||||
</template>
|
||||
</VxeColumn>
|
||||
</template>
|
||||
</SpuAndSkuList>
|
||||
</div>
|
||||
</template>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<!-- 商品选择弹窗 -->
|
||||
<SpuSkuSelect
|
||||
ref="spuSelectRef"
|
||||
:is-select-sku="true"
|
||||
@select="handleSpuSelected"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -138,6 +138,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
componentProps: {
|
||||
showTime: true,
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
valueFormat: 'x',
|
||||
placeholder: [
|
||||
$t('utils.rangePicker.beginTime'),
|
||||
$t('utils.rangePicker.endTime'),
|
||||
@@ -222,7 +223,10 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '优惠设置',
|
||||
component: 'Input',
|
||||
formItemClass: 'items-start',
|
||||
rules: 'required',
|
||||
rules: z
|
||||
.array(z.any())
|
||||
.min(1, { message: '请添加至少一条优惠规则' })
|
||||
.default([]),
|
||||
},
|
||||
{
|
||||
fieldName: 'productScopeValues', // 隐藏字段:用于自动同步 productScopeValues
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
PromotionConditionTypeEnum,
|
||||
PromotionProductScopeEnum,
|
||||
} from '@vben/constants';
|
||||
import { convertToInteger, formatToFraction } from '@vben/utils';
|
||||
import { cloneDeep, convertToInteger, formatToFraction } from '@vben/utils';
|
||||
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
@@ -52,6 +52,8 @@ const [Form, formApi] = useVbenForm({
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
// 在验证前同步 formData.rules 到表单中
|
||||
await formApi.setFieldValue('rules', formData.value.rules || []);
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
@@ -60,18 +62,24 @@ const [Modal, modalApi] = useVbenModal({
|
||||
// 提交表单
|
||||
try {
|
||||
const values = await formApi.getValues();
|
||||
const data = { ...formData.value, ...values };
|
||||
// 使用 formData.value 作为基础,确保 rules 来自 formData
|
||||
const data = { ...values, ...formData.value };
|
||||
if (data.startAndEndTime && Array.isArray(data.startAndEndTime)) {
|
||||
data.startTime = data.startAndEndTime[0];
|
||||
data.endTime = data.startAndEndTime[1];
|
||||
data.startTime = Number(data.startAndEndTime[0]);
|
||||
data.endTime = Number(data.startAndEndTime[1]);
|
||||
delete data.startAndEndTime;
|
||||
}
|
||||
data.rules?.forEach((item: any) => {
|
||||
// 深拷贝 rules 避免修改原始数据
|
||||
const rules = cloneDeep(
|
||||
data.rules,
|
||||
) as unknown as MallRewardActivityApi.RewardRule[];
|
||||
rules.forEach((item: any) => {
|
||||
item.discountPrice = convertToInteger(item.discountPrice || 0);
|
||||
if (data.conditionType === PromotionConditionTypeEnum.PRICE.type) {
|
||||
item.limit = convertToInteger(item.limit || 0);
|
||||
}
|
||||
});
|
||||
data.rules = rules;
|
||||
await (data.id
|
||||
? updateRewardActivity(data as MallRewardActivityApi.RewardActivity)
|
||||
: createRewardActivity(data as MallRewardActivityApi.RewardActivity));
|
||||
@@ -96,7 +104,11 @@ const [Modal, modalApi] = useVbenModal({
|
||||
modalApi.lock();
|
||||
try {
|
||||
const result = await getReward(data.id);
|
||||
result.startAndEndTime = [result.startTime, result.endTime] as any[];
|
||||
// valueFormat: 'x' 配置下,直接使用时间戳字符串
|
||||
result.startAndEndTime = [
|
||||
result.startTime ? String(result.startTime) : undefined,
|
||||
result.endTime ? String(result.endTime) : undefined,
|
||||
] as any[];
|
||||
result.rules?.forEach((item: any) => {
|
||||
item.discountPrice = formatToFraction(item.discountPrice || 0);
|
||||
if (result.conditionType === PromotionConditionTypeEnum.PRICE.type) {
|
||||
|
||||
@@ -43,7 +43,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
modalApi.lock();
|
||||
// 提交表单
|
||||
const data = (await formApi.getValues()) as PayChannelApi.Channel;
|
||||
data.config = JSON.stringify(data.config);
|
||||
data.config = JSON.stringify(data.config || {});
|
||||
try {
|
||||
await (data.id ? updateChannel(data) : createChannel(data));
|
||||
// 关闭并提示
|
||||
|
||||
Reference in New Issue
Block a user