提交 37afade8 authored 作者: lidongxu's avatar lidongxu

Merge branch 'release' into dev

......@@ -48,6 +48,7 @@
"devDependencies": {
"@vant/auto-import-resolver": "^1.3.0",
"@vitejs/plugin-vue": "5.0.5",
"@vitejs/plugin-vue-jsx": "^5.1.1",
"cz-conventional-changelog": "^3.3.0",
"postcss-pxtorem": "^6.1.0",
"sass": "1.77.5",
......
......@@ -19,7 +19,10 @@ export function submitDisplayPlan(data) {
url: `/operation/sales/ap_display/core/${data.id}`,
method: 'PUT',
data: {
display: data
display: {
...data,
id: undefined // 不携带 id
}
}
});
}
......@@ -47,3 +50,57 @@ export function submitDisplaySchedulePlan(data) {
}
});
}
// 获取-档期陈列列表
export function getDisplayScheduleDetail(params) {
return request({
url: '/operation/sales/ap_display/query/pro_page',
params
})
}
// 填报-档期陈列
export function submitDisplayScheduleDetail(data) {
Object.keys(data).forEach(key => {
if (data[key] === undefined || data[key] === null) {
data[key] = ''
}
})
return request({
url: `/operation/sales/ap_display/core_pro/${data.id}`,
method: 'PUT',
data: {
display: {
...data,
id: undefined // 不携带 id
}
}
});
}
// 获取-零食计划列表
export function getSnackPlanList(params) {
return request({
url: '/operation/sales/ap_display/query/snack_page',
params
})
}
// 填报-零食计划
export function submitSnackPlan(data) {
Object.keys(data).forEach(key => {
if (data[key] === undefined || data[key] === null) {
data[key] = ''
}
})
return request({
url: `/operation/sales/ap_display/core_snack/${data.id}`,
method: 'PUT',
data: {
display: {
...data,
id: undefined // 不携带 id
}
}
});
}
\ No newline at end of file
......@@ -126,7 +126,7 @@ aside {
// 全局样式
// 容器
.app-container {
padding: 10px;
padding: 20px;
height: 100%;
// 内容区域
......@@ -134,7 +134,7 @@ aside {
width: 100%;
height: 100%;
background-color: var(--el-bg-color-overlay);
padding: 10px;
padding: 20px;
display: flex;
flex-direction: column;
}
......
<template>
<el-row>
<el-form-item>
<el-radio-group v-model="operation"
@change="checkTableColumns">
<el-radio-button label="平铺模式"
value="平铺模式" />
<el-radio-button label="填报模式"
value="填报模式" />
</el-radio-group>
</el-form-item>
<right-toolbar v-model:showSearch="showSearch"
@queryTable="getTableList()"
:columns="chooseColumns"
:showColumnsType="operation === '平铺模式' ? 'tree' : 'checkbox'"
:defaultCheckedKeys="visibleProps">
</right-toolbar>
</el-row>
<!-- 筛选列组的按钮 -->
<el-table :data="tableData"
border
ref="tableRef"
class="auto-fit-header-table"
v-loading="isLoading">
<template v-for="col in tableColumns">
<el-table-column v-if="col.visible"
:label="col.label"
:key="col.prop"
align="center"
:show-overflow-tooltip="col.type === 'string'"
class-name="column-style"
:width="col.width"
:fixed="operation === '填报模式' && col.fixed">
<template #header="{ column }">
<!-- 只为特定公式列添加问号图标 -->
<span class="formula-column">
{{ column.label }}
<el-tooltip v-if="col.type === 'formula'"
:content="col.formulaStr"
placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</span>
</template>
<template #default="{ row }">
<!-- 填报模式 -->
<div v-if="operation === '填报模式'" style="overflow: visible !important;">
<!-- 带选择框/输入框的操作单元格 -->
<div v-if="col.render"
class="cell-style">
<component :is="col.render(h, row, col)" />
</div>
<!-- 正常显示/公式计算 -->
<div v-else-if="col.type === 'formula'">
{{ col.func(row) }}
</div>
<div v-else>
{{ row[col.prop] || '-' }}
</div>
</div>
<!-- 平铺模式 -->
<div v-else>{{ row[col.prop] || '-' }}</div>
</template>
</el-table-column>
</template>
</el-table>
<!-- 分页 -->
<pagination :total="total"
v-model:page="params.pageNum"
v-model:limit="params.pageSize"
@pagination="getTableList" />
</template>
<script setup>
import { h } from 'vue'
/*************** 操作类型 ***************/
// const operation = ref('平铺模式');
const operation = ref('填报模式');
const tableRef = ref(null)
const props = defineProps({
tableColumns: { // 表格列
type: Array,
default: () => []
},
chooseColumns: { // 右上角工具选择列
type: Array,
default: () => []
},
visibleProps: { // 右上角工具为树结构,默认勾选的列
type: Array,
default: () => []
},
tableData: { // 数据源
type: Array,
default: () => []
},
isLoading: {
type: Boolean,
default: false
},
total: {
type: Number,
default: 0
},
params: {
type: Object,
default: () => ({
pageNum: 1,
pageSize: 10,
})
}
})
const emit = defineEmits(['updateColumns', 'getTableList'])
// 右上角工具
const showSearch = ref(true)
// 切换平铺/填报模式
const checkTableColumns = async () => {
// 通知外面传入 tableColumns / chooseColumns 数据源
await emit('updateColumns', operation.value)
// 强制表格立即应用所有宽度设置,避免先自适应再调整
nextTick(() => {
if (tableRef.value) {
tableRef.value.doLayout()
}
})
}
onMounted(() => {
checkTableColumns()
})
const getTableList = () => {
emit('getTableList')
}
</script>
<style scoped
lang="scss">
.container {
.el-tabs {
height: 100%;
display: flex;
flex-direction: column-reverse;
.el-tabs__content {
display: flex;
flex-direction: column;
height: 100%;
.el-tab-pane {
height: 100%;
display: flex;
flex-direction: column;
.pagination-container {
margin: 10px;
}
}
.formula-column {
display: flex;
justify-content: center;
align-items: center;
.el-icon {
margin-left: 2px;
}
}
}
}
.el-form-item {
align-items: center;
}
/* 表格区域 */
.auto-fit-header-table {
/* 优化超长文本的显示效果 */
.cell {
/* padding: 0 .2133rem; */
>div {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
::v-deep(.el-table__row) {
.el-table__cell {
padding: 0;
}
}
::v-deep(.column-style) {
.cell {
/* overflow: visible; */
>div {
/* overflow: visible !important; */
}
.cell-style {
margin: 0 -12px;
>div {
display: flex;
flex-direction: column;
align-items: flex-start;
>span {
text-align: left;
text-indent: 5px;
display: inline-block;
width: 100%;
background-color: #e1e2e6;
border-bottom: 1px solid #ebeef5;
}
}
/* 表格内下拉框 */
.el-select {
width: 100% !important;
padding: 10px;
}
.el-input {
padding: 10px;
}
.date-picker {
width: 100%;
padding: 10px;
.el-input {
width: 100%;
padding: 0;
}
}
}
}
}
}
}
/* 特殊动态渲染单元格样式 */
.render-cell {
p {
margin: 0;
}
}
</style>
\ No newline at end of file
......@@ -12,6 +12,14 @@
name="档期计划">
<Schedule />
</el-tab-pane>
<el-tab-pane label="档期陈列"
name="档期陈列">
<ScheduleDis />
</el-tab-pane>
<el-tab-pane label="零食计划"
name="零食计划">
<Snack />
</el-tab-pane>
</el-tabs>
</div>
</div>
......@@ -20,7 +28,9 @@
<script setup>
import Display from './tabs/display.vue'
import Schedule from './tabs/schedule.vue'
const activeName = ref('陈列计划');
import ScheduleDis from './tabs/schedule_dis.vue'
import Snack from './tabs/snack.vue'
const activeName = ref('档期陈列');
const handleClickTabs = (tab) => {
activeName.value = tab.name;
}
......
<template>
<el-row>
<el-form-item>
<el-radio-group v-model="operation"
@change="checkTableColumns">
<el-radio-button label="平铺模式"
value="平铺模式" />
<el-radio-button label="填报模式"
value="填报模式" />
</el-radio-group>
</el-form-item>
<right-toolbar v-model:showSearch="showSearch"
@queryTable="getTableList()"
:columns="chooseColumns"
:showColumnsType="operation === '平铺模式' ? 'tree' : 'checkbox'"
:defaultCheckedKeys="visibleProps">
</right-toolbar>
</el-row>
<!-- 筛选列组的按钮 -->
<el-table :data="tableData"
border
ref="tableRef"
class="auto-fit-header-table"
v-loading="isLoading">
<template v-for="col in tableColumns">
<el-table-column v-if="col.visible"
:label="col.label"
:key="col.prop"
align="center"
:show-overflow-tooltip="col.type === 'string'"
class-name="column-style"
:width="getColumnMinWidth(col)"
:fixed="operation === '填报模式' && col.fixed">
<template #header="{ column }">
<!-- 只为特定列添加问号图标 -->
<span class="formula-column">
{{ column.label }}
<el-tooltip v-if="col.type === 'formula'"
:content="col.formulaStr"
placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</span>
</template>
<template #default="{ row }">
<div v-if="col.type === 'select'"
class="cell-style">
<!-- 实际主货架-数量(要根据实际主货架-形式不为空时才可以选择否则为 0) -->
<div v-if="col.prop === 'actualMainShelfQty'">
<span>{{ row[col.referenceKey] || '-' }}</span>
<el-select :disabled="!row.actualMainShelfType || !row.plannedMainShelfQty"
v-model="row[col.prop]"
placeholder=""
clearable
@change="submitChange(row, col)">
<el-option v-for="item in col.options"
:key="item.value"
:label="item.label"
:value="item.value" />
</el-select>
</div>
<div v-else>
<span>{{ row[col.referenceKey] || '-' }}</span>
<el-select v-model="row[col.prop]"
:disabled="!row[col.referenceKey]"
placeholder=""
clearable
@change="submitChange(row, col)">
<el-option v-for="item in col.options"
:key="item.value"
:label="item.label"
:value="item.value" />
</el-select>
</div>
</div>
<!-- 公式计算 -->
<div v-else-if="col.type === 'formula'">
{{ col.func(row) }}
</div>
<!-- 输入框 -->
<div v-else-if="col.type === 'input'"
class="cell-style">
<div>
<span>{{ row[col.referenceKey] || '-' }}</span>
<el-input v-model="row[col.prop]"
:disabled="!row[col.referenceKey]"
placeholder=""
@input="submitChange(row, col)" />
</div>
</div>
<!-- 为其他类型或未定义类型提供默认显示 -->
<div v-else>
<div class="ellipsis-text"
style="max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
{{ formatterTableData(row, col) }}
</div>
</div>
</template>
</el-table-column>
</template>
</el-table>
<!-- 分页 -->
<pagination :total="total"
v-model:page="params.pageNum"
v-model:limit="params.pageSize"
@pagination="getTableList" />
<CommonPlan :tableData="tableData"
:isLoading="isLoading"
:total="total"
:tableColumns="tableColumns"
:chooseColumns="chooseColumns"
:visibleProps="visibleProps"
:params="params"
@updateColumns="updateColumns"
@getTableList="getTableList" />
</template>
<script setup>
import { getDisplayList, submitDisplayPlan } from '@/api'
import { parseTime } from '@/utils'
import { onMounted } from 'vue';
/*************** 操作类型 ***************/
const operation = ref('平铺模式');
const tableRef = ref(null)
// 全部列
const baseColumns = ref([
{
label: "基础信息",
children: [
{ label: '计划月份', prop: 'salesMonth', visible: true, type: 'string', fill: false, fixed: 'left' },
{ label: "销售大区", prop: "regionName", visible: true, type: 'string', fill: false },
{ label: "销售战区", prop: "districtName", visible: true, type: 'string', fill: false },
{ label: "经销商-省份", prop: "dealerProvince", visible: true, type: 'string', fill: false },
{ label: "经销商-城市", prop: "dealerCity", visible: true, type: 'string', fill: false },
{ label: "经销商-代码", prop: "dealerCode", visible: true, type: 'string', fill: false },
{ label: "经销商-类型", prop: "dealerType", visible: true, type: 'string', fill: false },
{ label: "开户日期", prop: "openingDate", visible: true, type: 'string', fill: false },
{ label: "闭户日期", prop: "closingDate", visible: true, type: 'string', fill: false },
{ label: "大区总监", prop: "regionManager", visible: true, type: 'string', fill: false },
{ label: "战区经理", prop: "districtManager", visible: true, type: 'string', fill: false },
{ label: "城市经理", prop: "cityManager", visible: true, type: 'string', fill: false },
{ label: "门店名称", prop: "storeName", visible: true, type: 'string', fill: true, fixed: 'left' },
{ label: "门店编码", prop: "storeCode", visible: true, type: 'string', fill: true, fixed: 'left' },
{ label: "经销商名称", prop: "dealerName", visible: true, type: 'string', fill: true, fixed: 'left' },
{ label: "系统类型", prop: "lineType", visible: true, type: 'string', fill: true, fixed: 'left' },
{ label: "门店-省份", prop: "storeProvince", visible: true, type: 'string', fill: false },
{ label: "门店-城市", prop: "storeCity", visible: true, type: 'string', fill: false },
{ label: "系统名称", prop: "lineName", visible: true, type: 'string', fill: true },
{ label: "渠道大类", prop: "channelDl", visible: true, type: 'string', fill: false },
{ label: "渠道小类", prop: "channelXl", visible: true, type: 'string', fill: false },
{ label: "门店类型", prop: "storeType", visible: true, type: 'string', fill: false },
{ label: "系统业态", prop: "systemFormat", visible: true, type: 'string', fill: false },
{ label: "门店面积", prop: "storeArea", visible: true, type: 'string', fill: false },
{ label: "门店分级(销量坎级)", prop: "storeLevel", visible: true, type: 'string', fill: false },
{ label: "门店地址", prop: "storeAddress", visible: true, type: 'string', fill: false },
{ label: "品项数", prop: "productCount", visible: true, type: 'string', fill: false }
],
prop: 'baseColumns',
visible: true
},
{
label: "大业态测试",
children: [
{ label: "大业态测试-动销模型", prop: "lfSalesModel", visible: true, type: 'string', fill: false },
{ label: "大业态测试-月均POS", prop: "lfMonthlyPos", visible: true, type: 'string', fill: false },
],
prop: 'lfColumns',
visible: true
},
{
label: "费用计划",
prop: 'fpColumns',
children: [
// { label: "计划主货架-形式", prop: "plannedMainShelfType", visible: true, type: 'string' },
// { label: "计划主货架-数量", prop: "plannedMainShelfQty", visible: true, type: 'string' },
{ label: "计划主货架-单个费用", prop: "plannedMainShelfUnitCost", visible: true, type: 'string', fill: false },
{ label: "计划主货架-总费用", prop: "plannedMainShelfTotalCost", visible: true, type: 'string', fill: false },
{
label: "实际主货架-形式",
prop: "actualMainShelfType",
visible: true,
type: 'select',
options: [
{ label: '4纵', value: '4纵' },
{ label: '5纵', value: '5纵' },
{ label: '6纵', value: '6纵' },
{ label: '7纵', value: '7纵' },
{ label: '8纵及以上', value: '8纵及以上' }
],
referenceKey: "plannedMainShelfType",
fill: true
},
{
label: "实际主货架-数量",
prop: "actualMainShelfQty",
visible: true,
type: 'select',
options: [
{ label: '1', value: 1 },
{ label: '2', value: 2 }
],
referenceKey: "plannedMainShelfQty",
fill: true
},
{
label: "实际主货架-是否执行",
prop: "actualMainShelfExecuted",
visible: true,
type: 'formula', // 公式
func: (row) => {
row.actualMainShelfExecuted = ((parseInt(row.actualMainShelfType) >= parseInt(row.plannedMainShelfType)) && (parseInt(row.actualMainShelfQty) >= parseInt(row.plannedMainShelfQty))) ? '是' : '否';
return row.actualMainShelfExecuted;
},
formulaStr: '公式:(实际主货架形式 >= 计划主货架形式)并且(实际主货架数量 >= 计划主货架数量)',
fill: true
},
// {
// label: "计划端架-数量", prop: "plannedEndCapQty", visible: true, type: 'string',
// fill: true
// },
{
label: "计划端架-总费用", prop: "plannedEndCapTotalCost", visible: true, type: 'string',
fill: false
},
{
label: "计划端架-单个费用", prop: "plannedEndCapUnitCost", visible: true, type: 'string',
fill: false
},
{
label: "实际端架-数量",
prop: "actualEndCapQty",
visible: true,
type: 'select',
options: [
{ label: '0', value: 0 },
{ label: '0.5', value: 0.5 },
{ label: '1', value: 1 },
{ label: '2', value: 2 }
],
referenceKey: "plannedEndCapQty",
fill: true
},
{
label: "实际端架-是否执行",
prop: "actualEndCapExecuted",
visible: true,
type: 'formula', // 公式
func: (row) => {
row.actualEndCapExecuted = ((parseFloat(row.actualEndCapQty) >= parseFloat(row.plannedEndCapQty))) ? '是' : '否';
return row.actualEndCapExecuted;
},
formulaStr: '公式:(实际端架数量 >= 计划端架数量)',
fill: true
},
{
label: "计划地堆-平米数(㎡)",
prop: "plannedFloorStackArea",
visible: true,
type: 'string',
fill: true
},
{
label: "计划地堆-数量", prop: "plannedFloorStackQty", visible: true, type: 'string',
fill: true
},
{
label: "计划主题地堆-是否", prop: "plannedThemedFloorStack", visible: true, type: 'string',
fill: true
},
{
label: "计划地堆-总费用", prop: "plannedFloorStackTotalCost", visible: true, type: 'string',
fill: false
},
{
label: "计划折算1㎡-单个费用", prop: "plannedFloorStackUnitCostPerSqm", visible: true, type: 'string',
fill: false
},
{
label: "实际地堆-平米数(㎡)",
prop: "actualFloorStackArea",
visible: true,
type: 'select',
options: [
{ label: '0', value: 0 },
{ label: '0.5', value: 0.5 },
{ label: '0.8', value: 0.8 },
{ label: '1', value: 1 },
{ label: '2', value: 2 },
{ label: '3', value: 3 },
{ label: '4', value: 4 },
],
referenceKey: "plannedFloorStackArea",
fill: true
},
{
label: "实际地堆-数量",
prop: "actualFloorStackQty",
visible: true,
type: 'select',
options: [
{ label: '0', value: 0 },
{ label: '1', value: 1 },
{ label: '2', value: 2 },
{ label: '3', value: 3 }
],
referenceKey: "plannedFloorStackQty",
fill: true
},
{
label: "实际主题地堆-是否",
prop: "actualThemedFloorStack",
visible: true,
type: 'select',
options: [
{ label: '是', value: '是' },
{ label: '否', value: '否' },
],
referenceKey: "plannedThemedFloorStack",
fill: true
},
{
label: "实际地堆是否执行",
prop: "actualFloorStackExecuted",
visible: true,
type: 'formula', // 公式
func: (row) => {
row.actualFloorStackExecuted = ((parseFloat(row.actualFloorStackArea) >= parseFloat(row.plannedFloorStackArea)) && (parseInt(row.actualFloorStackQty) >= parseInt(row.plannedFloorStackQty))) ? '是' : '否';
return row.actualFloorStackExecuted;
},
formulaStr: '公式:(实际地堆平米数 >= 计划地堆平米数)并且(实际地堆数量 >= 计划地堆数量)',
fill: true
},
// {
// label: "计划多点陈列-数量+形式", prop: "plannedMultiDisplay", visible: true, type: 'string',
// fill: true
// },
{
label: "计划多点陈列-总费用", prop: "plannedMultiDisplayTotalCost", visible: true, type: 'string',
fill: false
},
{
label: "实际多点陈列-数量+形式",
prop: "actualMultiDisplay",
visible: true,
type: 'input',
func: (row) => {
// 没有输入内容时,则是否执行设置为否
row.actualMultiDisplayExecuted = !row.actualMultiDisplay ? '否' : '是'
},
referenceKey: "plannedMultiDisplay",
fill: true
},
{
label: "实际多点陈列-是否执行",
prop: "actualMultiDisplayExecuted",
visible: true,
type: 'select',
options: [
{ label: '是', value: '是' },
{ label: '否', value: '否' },
],
fill: true
},
{
label: "合计费用-费用", prop: "totalCost", visible: true, type: 'string',
fill: false
},
{
label: "合计费用-费率", prop: "totalCostRate", visible: true, type: 'string',
fill: false
},
{
label: "常规陈列是否执行",
prop: "regularDisplayExecuted",
visible: true,
type: 'formula', // 公式
func: (row) => {
// 实际主货架-是否执行 && 实际端架-是否执行 && 实际地堆-是否执行 && 实际多点陈列-是否执行
row.regularDisplayExecuted = (row.actualMainShelfExecuted === '是' && row.actualEndCapExecuted === '是' && row.actualFloorStackExecuted === '是' && row.actualMultiDisplayExecuted === '是') ? '是' : '否';
return row.regularDisplayExecuted;
},
formulaStr: '公式:(实际主货架是否执行,并且实际端架是否执行,并且实际地堆是否执行,并且实际多点陈列是否执行)',
fill: true
},
{
label: "综合标签-拜访辅助列", prop: "visitAssistTag", visible: true, type: 'string',
fill: false
},
{
label: "付费陈列- 是否", prop: "paidDisplay", visible: true, type: 'string',
fill: false
},
{
label: "当月拜访目标", prop: "monthlyVisitTarget", visible: true, type: 'string',
fill: false
},
{
label: "当月是否拜访", prop: "monthlyVisited", visible: true, type: 'string',
fill: false
<script setup
lang="jsx">
import CommonPlan from '@/views/promotion/components/CommonPlan'
import { getDisplayList, submitDisplayPlan } from '@/api'
import { parseTime } from '@/utils'
import { onMounted } from 'vue';
/*************** 操作类型 ***************/
// 全部列
const baseColumns = ref([
// 一级类
{
label: "基础信息", // 类型标题
children: [
{
label: '计划月份', // 列标题
prop: 'salesMonth', // 列属性
visible: true, // 是否显示
type: 'string', // 列类型
fill: true, // 是否为填报列
fixed: 'left', // 是否固定在左侧
width: 90
},
{
label: "销售大区",
prop: "regionName",
visible: true,
type: 'string',
fill: false,
width: 90
},
{
label: "销售战区",
prop: "districtName",
visible: true,
type: 'string',
fill: false,
width: 90
},
{
label: "经销商-省份",
prop: "dealerProvince",
visible: true,
type: 'string',
fill: false,
width: 105
},
{
label: "经销商-城市",
prop: "dealerCity",
visible: true,
type: 'string',
fill: false,
width: 105
},
{
label: "经销商-代码",
prop: "dealerCode",
visible: true,
type: 'string',
fill: false,
width: 105
},
{
label: "经销商-类型",
prop: "dealerType",
visible: true,
type: 'string',
fill: false,
width: 145
},
{
label: "开户日期",
prop: "openingDate",
visible: true,
type: 'string',
fill: false,
width: 105
},
{
label: "闭户日期",
prop: "closingDate",
visible: true,
type: 'string',
fill: false,
width: 105
},
{
label: "大区总监",
prop: "regionManager",
visible: true,
type: 'string',
fill: false,
width: 90
},
{
label: "战区经理",
prop: "districtManager",
visible: true,
type: 'string',
fill: false,
width: 90
},
{
label: "城市经理",
prop: "cityManager",
visible: true,
type: 'string',
fill: false,
width: 90
},
{
label: "门店名称",
prop: "storeNameCodeDealerName", // 新增动态列(只在填报列显示)
visible: true,
type: 'string',
fill: true,
onlyFill: true,// 仅仅在填报模式下生效
fixed: 'left',
width: 215,
render: (h, row) => {
return h('div', {
class: 'render-cell',
style: {
alignItems: 'center',
whiteSpace: 'pre-line'
}
}, [
h('p', row.storeName),
h('p', row.storeCode),
h('p', {
style: {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
maxWidth: '100%'
}
}, row.dealerName)
])
}
},
{
label: "门店名称",
prop: "storeName",
visible: true,
type: 'string',
fill: false,
fixed: 'left',
width: 175
},
{
label: "门店编码",
prop: "storeCode",
visible: true,
type: 'string',
fill: false,
fixed: 'left',
width: 105
},
{
label: "经销商名称",
prop: "dealerName",
visible: true,
type: 'string',
fill: false,
fixed: 'left',
width: 215
},
{
label: "系统类型",
prop: "lineType",
visible: true,
type: 'string',
fill: false,
fixed: 'left',
width: 90
},
{
label: "门店-省份",
prop: "storeProvince",
visible: true,
type: 'string',
fill: false,
width: 90
},
{
label: "门店-城市",
prop: "storeCity",
visible: true,
type: 'string',
fill: false,
width: 90
},
{
label: "系统名称",
prop: "lineName",
visible: true,
type: 'string',
fill: false,
width: 90
},
{
label: "渠道大类",
prop: "channelDl",
visible: true,
type: 'string',
fill: false,
width: 90
},
{
label: "渠道小类",
prop: "channelXl",
visible: true,
type: 'string',
fill: false,
width: 140
},
{
label: "门店类型",
prop: "storeType",
visible: true,
type: 'string',
fill: false,
width: 90
},
{
label: "系统业态",
prop: "systemFormat",
visible: true,
type: 'string',
fill: false,
width: 90
},
{
label: "门店面积",
prop: "storeArea",
visible: true,
type: 'string',
fill: false,
width: 90
},
{
label: "门店分级(销量坎级)",
prop: "storeLevel",
visible: true,
type: 'string',
fill: false,
width: 165
},
{
label: "门店地址",
prop: "storeAddress",
visible: true,
type: 'string',
fill: false,
width: 255
},
{
label: "品项数",
prop: "productCount",
visible: true,
type: 'string',
fill: false,
width: 80
}
],
prop: 'baseColumns',
visible: true
},
{
label: "大业态测试",
children: [
{
label: "大业态测试-动销模型",
prop: "lfSalesModel",
visible: true,
type: 'string',
fill: false,
width: 155
},
{
label: "大业态测试-月均POS",
prop: "lfMonthlyPos",
visible: true,
type: 'string',
fill: false,
width: 155
}
],
prop: 'lfColumns',
visible: true
},
{
label: "费用计划",
prop: 'fpColumns',
children: [
{
label: "计划主货架-形式",
prop: "plannedMainShelfType",
visible: true,
type: 'string',
fill: false,
width: 130
},
{
label: "计划主货架-数量",
prop: "plannedMainShelfQty",
visible: true,
type: 'string',
fill: false,
width: 130
},
{
label: "计划主货架-单个费用",
prop: "plannedMainShelfUnitCost",
visible: true,
type: 'string',
fill: false,
width: 155
},
{
label: "计划主货架-总费用",
prop: "plannedMainShelfTotalCost",
visible: true,
type: 'string',
fill: false,
width: 145
},
{
label: "实际主货架-形式",
prop: "actualMainShelfType",
visible: true,
type: 'select',
options: [
{ label: '4纵', value: '4纵' },
{ label: '5纵', value: '5纵' },
{ label: '6纵', value: '6纵' },
{ label: '7纵', value: '7纵' },
{ label: '8纵及以上', value: '8纵及以上' }
],
referenceKey: "plannedMainShelfType",
fill: true,
width: 140,
render: (_, row, col) => {
return (
<div>
<span>{row[col.referenceKey] || '-'}</span>
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
placeholder=""
clearable>
{col.options.map(item => (
<el-option
key={item.value}
label={item.label}
value={item.value}
/>
))}
</el-select>
</div>
)
},
// 请求时需要额外携带影响的列字段值
requestKey: ["actualMainShelfQty", "actualMainShelfExecuted", "regularDisplayExecuted"]
},
{
label: "实际主货架-数量",
prop: "actualMainShelfQty",
visible: true,
type: 'select',
options: [
{ label: '0', value: 0 },
{ label: '1', value: 1 },
{ label: '2', value: 2 }
],
referenceKey: "plannedMainShelfQty",
fill: true,
width: 130,
render: (_, row, col) => {
return (
<div>
<span>{row[col.referenceKey] || '-'}</span>
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
placeholder="">
{col.options.map(item => (
<el-option
key={item.value}
label={item.label}
value={item.value}
/>
))}
</el-select>
</div>
)
},
requestKey: ["actualMainShelfExecuted", "regularDisplayExecuted"]
},
{
label: "实际主货架-是否执行",
prop: "actualMainShelfExecuted",
visible: true,
type: 'formula',
func: (row) => {
row.actualMainShelfExecuted = ((parseInt(row.actualMainShelfType) >= parseInt(row.plannedMainShelfType)) && (parseInt(row.actualMainShelfQty) >= parseInt(row.plannedMainShelfQty))) ? '是' : '否';
return row.actualMainShelfExecuted;
},
formulaStr: '公式:(实际主货架形式 >= 计划主货架形式)并且(实际主货架数量 >= 计划主货架数量)',
fill: true,
width: 170
},
{
label: "计划端架-数量",
prop: "plannedEndCapQty",
visible: true,
type: 'string',
fill: false,
width: 115
},
{
label: "计划端架-总费用",
prop: "plannedEndCapTotalCost",
visible: true,
type: 'string',
fill: false,
width: 130
},
{
label: "计划端架-单个费用",
prop: "plannedEndCapUnitCost",
visible: true,
type: 'string',
fill: false,
width: 140
},
{
label: "实际端架-数量",
prop: "actualEndCapQty",
visible: true,
type: 'select',
options: [
{ label: '0', value: 0 },
{ label: '0.5', value: 0.5 },
{ label: '1', value: 1 },
{ label: '2', value: 2 }
],
referenceKey: "plannedEndCapQty",
fill: true,
width: 115,
render: (_, row, col) => {
return (
<div>
<span>{row[col.referenceKey] || '-'}</span>
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
placeholder="">
{col.options.map(item => (
<el-option
key={item.value}
label={item.label}
value={item.value}
/>
))}
</el-select>
</div>
)
},
// 请求时需要额外携带影响的列字段值
requestKey: ["actualEndCapExecuted", "regularDisplayExecuted"]
},
{
label: "实际端架-是否执行",
prop: "actualEndCapExecuted",
visible: true,
type: 'formula',
func: (row) => {
row.actualEndCapExecuted = ((parseFloat(row.actualEndCapQty) >= parseFloat(row.plannedEndCapQty))) ? '是' : '否';
return row.actualEndCapExecuted;
},
formulaStr: '公式:(实际端架数量 >= 计划端架数量)',
fill: true,
width: 155
},
{
label: "计划地堆-平米数(㎡)",
prop: "plannedFloorStackArea",
visible: true,
type: 'string',
fill: false,
width: 170
},
{
label: "计划地堆-数量",
prop: "plannedFloorStackQty",
visible: true,
type: 'string',
fill: false,
width: 115
},
{
label: "计划主题地堆-是否",
prop: "plannedThemedFloorStack",
visible: false,
type: 'string',
fill: true,
width: 140
},
{
label: "计划地堆-总费用",
prop: "plannedFloorStackTotalCost",
visible: true,
type: 'string',
fill: false,
width: 130
},
{
label: "计划折算1㎡-单个费用",
prop: "plannedFloorStackUnitCostPerSqm",
visible: true,
type: 'string',
fill: false,
width: 160
},
{
label: "实际地堆-平米数(㎡)",
prop: "actualFloorStackArea",
visible: true,
type: 'select',
options: [
{ label: '0', value: 0 },
{ label: '0.5', value: 0.5 },
{ label: '0.8', value: 0.8 },
{ label: '1', value: 1 },
{ label: '2', value: 2 },
{ label: '3', value: 3 },
{ label: '4', value: 4 }
],
referenceKey: "plannedFloorStackArea",
fill: true,
width: 170,
render: (_, row, col) => {
return (
<div>
<span>{row[col.referenceKey] || '-'}</span>
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
placeholder="">
{col.options.map(item => (
<el-option
key={item.value}
label={item.label}
value={item.value}
/>
))}
</el-select>
</div>
)
},
// 请求时需要额外携带影响的列字段值
requestKey: ["actualFloorStackExecuted", "regularDisplayExecuted"]
},
{
label: "实际地堆-数量",
prop: "actualFloorStackQty",
visible: true,
type: 'select',
options: [
{ label: '0', value: 0 },
{ label: '1', value: 1 },
{ label: '2', value: 2 },
{ label: '3', value: 3 }
],
referenceKey: "plannedFloorStackQty",
fill: true,
width: 115,
render: (_, row, col) => {
return (
<div>
<span>{row[col.referenceKey] || '-'}</span>
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row.actualFloorStackArea || !row[col.referenceKey]}
placeholder="">
{col.options.map(item => (
<el-option
key={item.value}
label={item.label}
value={item.value}
/>
))}
</el-select>
</div>
)
},
// 请求时需要额外携带影响的列字段值
requestKey: ["actualFloorStackExecuted", "regularDisplayExecuted"]
},
{
label: "实际主题地堆-是否",
prop: "actualThemedFloorStack",
visible: true,
type: 'select',
options: [
{ label: '是', value: '是' },
{ label: '否', value: '否' }
],
fill: true,
width: 140,
render: (_, row, col) => {
return (
<div>
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
placeholder="">
{col.options.map(item => (
<el-option
key={item.value}
label={item.label}
value={item.value}
/>
))}
</el-select>
</div>
)
}
},
{
label: "实际地堆是否执行",
prop: "actualFloorStackExecuted",
visible: true,
type: 'formula',
func: (row) => {
row.actualFloorStackExecuted = ((parseFloat(row.actualFloorStackArea) >= parseFloat(row.plannedFloorStackArea)) && (parseInt(row.actualFloorStackQty) >= parseInt(row.plannedFloorStackQty))) ? '是' : '否';
return row.actualFloorStackExecuted;
},
formulaStr: '公式:(实际地堆平米数 >= 计划地堆平米数)并且(实际地堆数量 >= 计划地堆数量)',
fill: true,
width: 150
},
{
label: "计划多点陈列-数量+形式",
prop: "plannedMultiDisplay",
visible: true,
type: 'string',
fill: false,
width: 175
},
{
label: "计划多点陈列-总费用",
prop: "plannedMultiDisplayTotalCost",
visible: true,
type: 'string',
fill: false,
width: 155
},
{
label: "实际多点陈列-数量+形式",
prop: "actualMultiDisplay",
visible: true,
type: 'input',
func: (row) => {
// 没有输入内容时,则是否执行设置为否
row.actualMultiDisplayExecuted = !row.actualMultiDisplay ? '否' : '是'
},
referenceKey: "plannedMultiDisplay",
fill: true,
width: 175,
render: (_, row, col) => {
return (
<div>
<span>{row[col.referenceKey] || '-'}</span>
<el-input modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
placeholder="">
</el-input>
</div>
)
},
// 请求时需要额外携带影响的列字段值
requestKey: ["actualMultiDisplayExecuted"]
},
{
label: "实际多点陈列-是否执行",
prop: "actualMultiDisplayExecuted",
visible: true,
type: 'select',
options: [
{ label: '是', value: '是' },
{ label: '否', value: '否' }
],
fill: true,
width: 170,
render: (_, row, col) => {
return (
<div>
<span>{row[col.referenceKey] || '-'}</span>
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
placeholder="">
{col.options.map(item => (
<el-option
key={item.value}
label={item.label}
value={item.value}
/>
))}
</el-select>
</div>
)
},
// 请求时需要额外携带影响的列字段值
requestKey: ["actualMultiDisplayExecuted"]
},
{
label: "合计费用-费用",
prop: "totalCost",
visible: true,
type: 'string',
fill: false,
width: 115
},
{
label: "合计费用-费率",
prop: "totalCostRate",
visible: true,
type: 'string',
fill: false,
width: 115
},
{
label: "常规陈列是否执行",
prop: "regularDisplayExecuted",
visible: true,
type: 'formula',
func: (row) => {
// 实际主货架-是否执行 && 实际端架-是否执行 && 实际地堆-是否执行 && 实际多点陈列-是否执行
row.regularDisplayExecuted = (row.actualMainShelfExecuted === '是' && row.actualEndCapExecuted === '是' && row.actualFloorStackExecuted === '是' && row.actualMultiDisplayExecuted === '是') ? '是' : '否';
return row.regularDisplayExecuted;
},
formulaStr: '公式:(实际主货架是否执行,并且实际端架是否执行,并且实际地堆是否执行,并且实际多点陈列是否执行)',
fill: true,
width: 150
},
// {
// label: "综合标签-拜访辅助列",
// prop: "visitAssistTag",
// visible: true,
// type: 'string',
// fill: false,
// width: 160
// },
{
label: "付费陈列- 是否",
prop: "paidDisplay",
visible: true,
type: 'string',
fill: false,
width: 120
},
{
label: "计划-当月拜访目标",
prop: "monthlyVisitTarget",
visible: true,
type: 'string',
fill: false,
width: 135
},
{
label: "实际-当月是否拜访",
prop: "monthlyVisited",
visible: true,
type: 'string',
fill: false,
width: 135
},
{
label: '修改人',
prop: 'updateBy',
visible: true,
type: 'string',
fill: false,
width: 120
},
{
label: '修改时间',
prop: 'updateTime',
visible: true,
type: 'string',
fill: false,
width: 140
}
],
visible: true
}
]);
// 表格列
const tableColumns = ref([])
// 右上角工具选择列
const chooseColumns = ref([])
// 计算列具体数据
const updateColumns = (operation) => {
return new Promise((resolve, reject) => {
if (operation === '平铺模式') {
chooseColumns.value = baseColumns.value
tableColumns.value = baseColumns.value.flatMap(item => {
if (item.children) {
return item.children.filter(child => !child.onlyFill);
}
return [];
});
} else if (operation === '填报模式') {
chooseColumns.value = baseColumns.value.flatMap(item => {
if (item.children) {
return item.children.filter(child => child.fill === true)
}
return [];
});
tableColumns.value = chooseColumns.value
}
],
visible: true
resolve()
})
}
]);
// 表格列
const tableColumns = ref([])
// 右上角工具选择列
const chooseColumns = ref([])
// 计算列具体数据
const checkTableColumns = () => {
if (operation.value === '平铺模式') {
chooseColumns.value = baseColumns.value
tableColumns.value = baseColumns.value.flatMap(item => {
if (item.children) {
return item.children.filter(child => child);
}
return [];
});
} else if (operation.value === '填报模式') {
chooseColumns.value = baseColumns.value.flatMap(item => {
onMounted(() => {
updateColumns()
})
// 树形结构选择列默认有哪些
const visibleProps = computed(() => {
return baseColumns.value.flatMap(item => {
if (item.children) {
return item.children
.filter(child => child.fill)
return item.children.filter(child => child.visible).map(child => child.prop);
}
return [];
return item.prop;
});
tableColumns.value = chooseColumns.value
}
});
// 强制表格立即应用所有宽度设置,避免先自适应再调整
nextTick(() => {
if (tableRef.value) {
tableRef.value.doLayout()
// 提交变更
const submitChange = async (row, col) => {
// 需要特殊处理的
// 1. 实际主货架-形式,为空时,置空实际主货架-数量
if (col.prop === 'actualMainShelfType' && !row.actualMainShelfType) {
row.actualMainShelfQty = 0
}
})
}
onMounted(() => {
checkTableColumns()
})
// 树形结构选择列默认有哪些
const visibleProps = computed(() => {
return baseColumns.value.flatMap(item => {
if (item.children) {
return item.children.filter(child => child.visible).map(child => child.prop);
let requestObj = {}
if (col.requestKey) {
// 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) {
const obj = tableColumns.value.find(item => item.prop === str)
if (obj.type === 'formula') {
obj.func(row)
}
}
requestObj = col.requestKey.reduce((acc, key) => ({ ...acc, [key]: row[key] }), {})
}
return item.prop;
});
});
// 提交变更
const submitChange = async (row, col) => {
// 有个类型输入框的需要调用 func
if (col.type === 'input') {
col.func(row)
}
await submitDisplayPlan({
id: row.sadId,
[col.prop]: row[col.prop]
})
// 这些提交变化的,同时会影响公式计算的,需要把公式计算的字段列结果,一起发给后台
// 注意:实际主货架形式改变并为空时,要置空实际主货架数量
if (col.prop === 'actualMainShelfType' && !row.actualMainShelfType) {
row.actualMainShelfQty = ''
// 并提交保存一次
await submitDisplayPlan({
id: row.sadId,
actualMainShelfQty: row.actualMainShelfQty,
[col.prop]: row[col.prop], // 当前修改列的值
...requestObj
})
}
// // 这些提交变化的,同时会影响公式计算的,需要把公式计算的字段列结果,一起发给后台
// // 注意:实际主货架形式改变并为空时,要置空实际主货架数量
// if (col.prop === 'actualMainShelfType' && !row.actualMainShelfType) {
// row.actualMainShelfQty = ''
// // 并提交保存一次
// await submitDisplayPlan({
// id: row.sadId,
// actualMainShelfQty: row.actualMainShelfQty,
// })
// }
// 主货架
if (['actualMainShelfType', 'actualMainShelfQty'].includes(col.prop)) {
await submitDisplayPlan({
id: row.sadId,
actualMainShelfExecuted: row.actualMainShelfExecuted,
})
// // 主货架
// if (['actualMainShelfType', 'actualMainShelfQty'].includes(col.prop)) {
// await submitDisplayPlan({
// id: row.sadId,
// actualMainShelfExecuted: row.actualMainShelfExecuted,
// })
// }
// // 端架
// if (['actualEndCapQty'].includes(col.prop)) {
// await submitDisplayPlan({
// id: row.sadId,
// actualEndCapExecuted: row.actualEndCapExecuted,
// })
// }
// // 地堆
// if (['actualFloorStackArea', 'actualFloorStackQty'].includes(col.prop)) {
// await submitDisplayPlan({
// id: row.sadId,
// actualFloorStackExecuted: row.actualFloorStackExecuted,
// })
// }
// // 常规陈列是否执行
// await submitDisplayPlan({
// id: row.sadId,
// regularDisplayExecuted: row.regularDisplayExecuted,
// })
}
// 端架
if (['actualEndCapQty'].includes(col.prop)) {
await submitDisplayPlan({
id: row.sadId,
actualEndCapExecuted: row.actualEndCapExecuted,
// 表格数据
const tableData = ref([])
const isLoading = ref(true)
const params = reactive({
pageNum: 1,
pageSize: 20,
})
const total = ref(0)
// 筛选工具
const getTableList = async () => {
isLoading.value = true
const res = await getDisplayList({
...params
})
}
// 地堆
if (['actualFloorStackArea', 'actualFloorStackQty'].includes(col.prop)) {
await submitDisplayPlan({
id: row.sadId,
actualFloorStackExecuted: row.actualFloorStackExecuted,
res.data.rows.forEach(item => {
// 计划月份
item.salesMonth = parseTime(item.salesMonth, '{y}-{m}')
// 开户日期
item.openingDate = parseTime(item.openingDate, '{y}-{m}-{d}')
// 闭户日期
item.closingDate = parseTime(item.closingDate, '{y}-{m}-{d}')
// 动态新增列:门店名称+门店编码+经销山名称(填报模式下,合并到一起)
item.storeNameCodeDealerName = item.storeName + '\n(' + item.storeCode + ')' + '\n(' + item.dealerName + ')'
})
tableData.value = res.data.rows
total.value = res.data.total
isLoading.value = false
}
// 常规陈列是否执行
await submitDisplayPlan({
id: row.sadId,
regularDisplayExecuted: row.regularDisplayExecuted,
})
}
// 表格数据
const tableData = ref([])
const isLoading = ref(true)
const params = reactive({
pageNum: 1,
pageSize: 10,
})
const total = ref(0)
// 筛选工具
const showSearch = ref(true);
const getTableList = async () => {
isLoading.value = true
const res = await getDisplayList({
...params
})
res.data.rows.forEach(item => {
item.salesMonth = parseTime(item.salesMonth, '{y}-{m}')
})
tableData.value = res.data.rows
total.value = res.data.total
isLoading.value = false
}
getTableList()
// 列宽度
const getColumnMinWidth = (column) => {
// 根据列名或属性判断合适的最小宽度
const widthMap = {
'salesMonth': 85,
'regionName': 85,
'districtName': 85,
'dealerProvince': 100,
'dealerCity': 100,
'dealerCode': 100,
'dealerName': 210,
'dealerType': 140,
'openingDate': 100,
'closingDate': 100,
'regionManager': 85,
'districtManager': 85,
'cityManager': 85,
'storeCode': 100,
'storeName': 170,
'storeProvince': 85,
'storeCity': 85,
'lineName': 85,
'lineType': 85,
'channelDl': 85,
'channelXl': 135,
'storeType': 85,
'systemFormat': 85,
'storeArea': 85,
'storeLevel': 160,
'storeAddress': 250,
'productCount': 75,
'lfSalesModel': 150,
'lfMonthlyPos': 150,
'plannedMainShelfType': 125,
'plannedMainShelfQty': 125,
'plannedMainShelfUnitCost': 150,
'plannedMainShelfTotalCost': 140,
'actualMainShelfType': 135,
'actualMainShelfQty': 125,
'actualMainShelfExecuted': 165,
'plannedEndCapQty': 110,
'plannedEndCapTotalCost': 125,
'plannedEndCapUnitCost': 135,
'actualEndCapQty': 110,
'actualEndCapExecuted': 150,
'plannedFloorStackArea': 165,
'plannedFloorStackQty': 110,
'plannedThemedFloorStack': 135,
'plannedFloorStackTotalCost': 125,
'plannedFloorStackUnitCostPerSqm': 155,
'actualFloorStackArea': 165,
'actualFloorStackQty': 110,
'actualThemedFloorStack': 135,
'actualFloorStackExecuted': 145,
'plannedMultiDisplay': 170,
'plannedMultiDisplayTotalCost': 150,
'actualMultiDisplay': 170,
'actualMultiDisplayExecuted': 165,
'totalCost': 110,
'totalCostRate': 110,
'regularDisplayExecuted': 145,
'visitAssistTag': 155,
'paidDisplay': 115,
'monthlyVisitTarget': 105,
'monthlyVisited': 105
};
return widthMap[column.prop] || 100; // 默认宽度
};
// 格式化函数
const formatterTableData = (row, col) => {
if (col.prop === 'openingDate' || col.prop === 'closingDate') {
return parseTime(row[col.prop], '{y}-{m}-{d}')
}
return row[col.prop] || '-'
}
getTableList()
</script>
<style scoped
......
<template>
<el-row>
<el-form-item>
<el-radio-group v-model="operation"
@change="checkTableColumns">
<el-radio-button label="平铺模式"
value="平铺模式" />
<el-radio-button label="填报模式"
value="填报模式" />
</el-radio-group>
</el-form-item>
<right-toolbar v-model:showSearch="showSearch"
@queryTable="getTableList()"
:columns="chooseColumns"
:showColumnsType="operation === '平铺模式' ? 'tree' : 'checkbox'"
:defaultCheckedKeys="visibleProps">
</right-toolbar>
</el-row>
<!-- 筛选列组的按钮 -->
<el-table :data="tableData"
border
ref="tableRef"
class="auto-fit-header-table"
v-loading="isLoading">
<template v-for="col in tableColumns">
<el-table-column v-if="col.visible"
:label="col.label"
:key="col.prop"
align="center"
:show-overflow-tooltip="col.type === 'string'"
class-name="column-style"
:width="getColumnMinWidth(col)"
:fixed="operation === '填报模式' && col.fixed">
<template #header="{ column }">
<!-- 只为特定列添加问号图标 -->
<span class="formula-column">
{{ column.label }}
<el-tooltip v-if="col.type === 'formula'"
:content="col.formulaStr"
placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</span>
</template>
<template #default="{ row }">
<div v-if="col.type === 'select'"
class="cell-style">
<div>
<el-tooltip effect="dark"
:content="row[col.referenceKey]"
placement="top-start"
:disabled="!isContentOverflow(col.referenceKey, row[col.referenceKey])">
<span class="ellipsis-tooltip">{{ row[col.referenceKey] || '-' }}</span>
</el-tooltip>
<el-select v-model="row[col.prop]"
:disabled="!row[col.referenceKey]"
placeholder=""
clearable
@change="submitChange(row, col)">
<el-option v-for="item in col.options"
:key="item.value"
:label="item.label"
:value="item.value" />
</el-select>
</div>
</div>
<!-- 公式计算 -->
<div v-else-if="col.type === 'formula'">
{{ col.func(row) }}
</div>
<!-- 输入框 -->
<div v-else-if="col.type === 'input'"
class="cell-style">
<div>
<el-tooltip effect="dark"
:content="row[col.referenceKey]"
placement="top-start"
:disabled="!isContentOverflow(col.referenceKey, row[col.referenceKey])">
<span class="ellipsis-tooltip">{{ row[col.referenceKey] || '-' }}</span>
</el-tooltip>
<el-input v-model="row[col.prop]"
:disabled="!row[col.referenceKey]"
placeholder=""
:type="col.format === 'number' ? 'number' : 'text'"
@input="submitChange(row, col)" />
</div>
</div>
<!-- 日期选择 -->
<div v-else-if="col.type === 'date'"
class="cell-style">
<div>
<span class="ellipsis-tooltip">{{ row[col.referenceKey] || '-' }}</span>
<div class="date-picker">
<el-date-picker v-model="row[col.prop]"
type="date"
:disabled="!row[col.referenceKey]"
:format="col.format"
<CommonPlan :tableData="tableData"
:isLoading="isLoading"
:total="total"
:tableColumns="tableColumns"
:chooseColumns="chooseColumns"
:visibleProps="visibleProps"
:params="params"
@updateColumns="updateColumns"
@getTableList="getTableList" />
</template>
<script setup
lang="jsx">
import CommonPlan from '@/views/promotion/components/CommonPlan'
import { getDisplayScheduleList, submitDisplaySchedulePlan } from '@/api'
import { parseTime } from '@/utils'
/*************** 操作类型 ***************/
function splitAndFilter(str) {
// 分割字符串并处理空值
return str.split(/[,,]/)
.map(item => item.trim()) // 去除前后空格
.filter(item => item.length > 0); // 过滤空字符串
}
// 基础列配置
const baseColumns = ref([
{
label: "基础信息",
children: [
{
label: '计划月份',
prop: 'salesMonth',
visible: true,
type: 'string',
fill: false,
fixed: 'left',
width: 90
},
{
label: "销售大区",
prop: "regionName",
visible: true,
type: "string",
fill: false,
width: 90
},
{
label: "销售战区",
prop: "districtName",
visible: true,
type: "string",
fill: false,
width: 90
},
{
label: "经销商-省份",
prop: "dealerProvince",
visible: true,
type: "string",
fill: false,
width: 105
},
{
label: "经销商-城市",
prop: "dealerCity",
visible: true,
type: "string",
fill: false,
width: 105
},
{
label: "经销商-代码",
prop: "dealerCode",
visible: true,
type: "string",
fill: false,
width: 105
},
{
label: "经销商名称",
prop: "dealerName",
visible: true,
type: "string",
fill: true,
fixed: 'left',
width: 215
},
{
label: "经销商类型",
prop: "dealerType",
visible: true,
type: "string",
fill: false,
width: 115
},
{
label: "开户日期",
prop: "openingDate",
visible: true,
type: "string",
fill: false,
width: 105
},
{
label: "闭户日期",
prop: "closingDate",
visible: true,
type: "string",
fill: false,
width: 105
},
{
label: "大区总监",
prop: "regionManager",
visible: true,
type: "string",
fill: false,
width: 90
},
{
label: "战区经理",
prop: "districtManager",
visible: true,
type: "string",
fill: false,
width: 90
},
{
label: "城市经理",
prop: "cityManager",
visible: true,
type: "string",
fill: false,
width: 90
},
{
label: "系统名称",
prop: "lineName",
visible: true,
type: "string",
fill: true,
fixed: 'left',
width: 90
},
{
label: "系统类型",
prop: "lineType",
visible: true,
type: "string",
fill: false,
width: 90
},
{
label: "渠道大类",
prop: "channelDl",
visible: true,
type: "string",
fill: false,
width: 90
},
{
label: "渠道小类",
prop: "channelXl",
visible: true,
type: "string",
fill: false,
width: 140
},
{
label: "系统业态",
prop: "lineLf",
visible: true,
type: "string",
fill: false,
width: 90
},
{
label: "门店数",
prop: "storeCount",
visible: true,
type: "string",
fill: false,
width: 75
}
],
prop: 'baseColumns',
visible: true
},
{
label: "档期基础信息",
children: [
{
label: "档期执行月份",
prop: "promotionExecutionMonth",
visible: true,
type: "string",
fill: false,
width: 110
},
{
label: "档期计划-促销规格",
prop: "plannedPromotionSpec",
visible: true,
type: "string",
fill: false,
width: 140
},
{
label: "档期计划-促销口味",
prop: "plannedPromotionFlavor",
visible: true,
type: "string",
fill: false,
width: 140
},
{
label: "档期执行-促销规格",
prop: "actualPromotionSpec",
visible: true,
type: 'select',
options: [
{
label: '虎皮105g',
value: '虎皮105g'
},
{
label: '虎皮210g',
value: '虎皮210g'
},
{
label: '去骨72g',
value: '去骨72g'
},
{
label: '去骨138g',
value: '去骨138g'
},
{
label: '小鸡腿80g',
value: '小鸡腿80g'
},
{
label: '老卤95g',
value: '老卤95g'
},
{
label: '鸡肉豆堡',
value: '鸡肉豆堡'
},
{
label: '牛肉豆堡',
value: '牛肉豆堡'
}
],
referenceKey: "plannedPromotionSpec",
fill: true,
width: 190,
render: (_, row, col) => {
return (
<div>
<span>{row[col.referenceKey] || '-'}</span>
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
placeholder=""
@change="submitChange(row, col)" />
clearable
>
{col.options.map(item => (
<el-option
key={item.value}
label={item.label}
value={item.value}
/>
))}
</el-select>
</div>
)
},
// 请求时需要额外携带影响的列字段值
requestKey: ["specExecutionStatus", "promotionExecutionStatus"]
},
</div>
</div>
<!-- 为其他类型或未定义类型提供默认显示 -->
<div v-else>
<div class="ellipsis-text"
style="max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
{{ row[col.prop] }}
</div>
</div>
</template>
</el-table-column>
</template>
</el-table>
<!-- 分页 -->
<pagination :total="total"
v-model:page="params.pageNum"
v-model:limit="params.pageSize"
@pagination="getTableList" />
</template>
{
label: "档期规格是否执行",
prop: "specExecutionStatus",
visible: true,
type: 'formula',
func: (row) => {
row.specExecutionStatus = row.plannedPromotionSpec === row.actualPromotionSpec ? '是' : '否';
return row.specExecutionStatus;
},
formulaStr: '公式:(档期计划促销规格 = 档期执行促销规格)',
fill: true,
width: 150
},
{
label: "档期执行-促销口味",
prop: "actualPromotionFlavor",
visible: true,
type: 'select',
options: [],
referenceKey: "plannedPromotionFlavor",
fill: true,
width: 170,
render: (_, row, col) => {
// options 根据参考属性值来决定
let list = splitAndFilter(row[col.referenceKey])
let isFullFlavor = false
// 如果数组里有"全系列"/"全口味"则修改数组
if (list.includes('全口味')) {
list = [
{
label: '全口味',
value: '全口味'
},
{
label: '部分口味',
value: '部分口味'
}
]
isFullFlavor = true
} else {
list = list.map(item => ({
label: item,
value: item
}))
isFullFlavor = false
}
// span 标签超出宽度时显示省略号
return (
<div>
<el-tooltip placement="top" content={row[col.referenceKey] || '-'} disabled={!isContentOverflow(col.referenceKey, row[col.referenceKey])}>
<span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: inline-block; max-width: 100%;">
{row[col.referenceKey] || '-'}
</span>
</el-tooltip>
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
placeholder=""
clearable
multiple={!isFullFlavor}
collapse-tags={!isFullFlavor}
collapse-tags-tooltip={!isFullFlavor}>
{list.map(item => (
<el-option
key={item.value}
label={item.label}
value={item.value}
/>
))}
</el-select>
</div>
)
},
// 请求时需要额外携带影响的列字段值
requestKey: ["flavorExecutionStatus", "promotionExecutionStatus"]
},
{
label: "档期口味是否执行",
prop: "flavorExecutionStatus",
visible: true,
type: 'formula',
func: (row) => {
// 处理逗号的口味形成数组
let ppromotionFlavor = splitAndFilter(row.plannedPromotionFlavor)
if (!row.actualPromotionFlavor) {
// 判断空字符串
row.flavorExecutionStatus = '未执行';
} else if (row.actualPromotionFlavor?.length === 0) {
// 空数组
row.flavorExecutionStatus = '未执行';
} else {
if (row.actualPromotionFlavor === '全口味') {
row.flavorExecutionStatus = '执行';
} else if (row.actualPromotionFlavor?.length === ppromotionFlavor.length) {
row.flavorExecutionStatus = '执行';
} else if (row.actualPromotionFlavor?.length !== ppromotionFlavor?.length) {
row.flavorExecutionStatus = '部分执行';
}
}
return row.flavorExecutionStatus;
},
formulaStr: '公式:(档期计划促销口味 = 档期执行促销口味)',
fill: true,
width: 150
}
],
prop: 'scheduleInfo',
visible: true
},
{
label: "合计费用",
prop: 'totalCostParent',
children: [
{
label: "合计费用",
prop: "totalCost",
visible: true,
type: "string",
fill: false,
width: 105
},
{
label: "合计费率",
prop: "totalCostRate",
visible: true,
type: "string",
fill: false,
width: 85
}
],
visible: true
},
{
label: "促销计划",
prop: 'promotionPlan',
children: [
{
label: "计划-档期形式",
prop: "plannedPromotionFormat",
visible: true,
type: "string",
fill: false,
width: 115
},
{
label: "计划-核销方式",
prop: "plannedVerificationMethod",
visible: true,
type: "string",
fill: false,
width: 115
},
{
label: "计划-档期规则",
prop: "plannedPromotionRules",
visible: true,
type: "string",
fill: false,
width: 115
},
{
label: "计划-档期前补差天数",
prop: "plannedPrePromotionAdjustDays",
visible: true,
type: "string",
fill: false,
width: 155
},
{
label: "计划-档期后补差天数",
prop: "plannedPostPromotionAdjustDays",
visible: true,
type: "string",
fill: false,
width: 155
},
{
label: "计划-档期开始时间",
prop: "plannedPromotionStartDate",
visible: true,
type: "string",
fill: false,
width: 140
},
{
label: "计划-补差开始时间",
prop: "plannedAdjustmentStartDate",
visible: true,
type: "string",
fill: false,
width: 140
},
{
label: "实际-档期开始时间",
prop: "actualPromotionStartDate",
visible: true,
type: 'date',
format: 'MM-DD',
referenceKey: "plannedPromotionStartDate",
fill: true,
width: 140,
render: (_, row, col) => {
return (
<div>
<span>{row[col.referenceKey] || '-'}</span>
<div class="date-picker">
<el-date-picker modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
placeholder=""
clearable
type="date"
value-format="MM-DD"
/>
</div>
</div>
)
}
},
{
label: "计划-档期结束时间",
prop: "plannedPromotionEndDate",
visible: true,
type: "string",
fill: false,
width: 140,
},
{
label: "计划-补差结束时间",
prop: "plannedAdjustmentEndDate",
visible: true,
type: "string",
fill: false,
width: 140
},
{
label: "实际-档期结束时间",
prop: "actualPromotionEndDate",
visible: true,
type: 'date',
format: 'MM-DD',
fill: true,
referenceKey: "plannedPromotionEndDate",
width: 140,
render: (_, row, col) => {
return (
<div>
<span>{row[col.referenceKey] || '-'}</span>
<div class="date-picker">
<el-date-picker modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
placeholder=""
clearable
type="date"
value-format="MM-DD"
/>
</div>
</div>
)
},
requestKey: ["plannedPromotionEndDate"]
},
{
label: "档期是否开展",
prop: "promotionImplementationStatus",
visible: true,
type: 'formula',
func: (row) => {
row.promotionImplementationStatus = row.actualPromotionStartDate && row.actualPromotionEndDate ? '是' : '否';
return row.promotionImplementationStatus;
},
formulaStr: '公式:(实际档期开始时间不为空,并且实际档期结束时间不为空)',
fill: true,
width: 125
},
{
label: "时间是否执行",
prop: "timeExecutionStatus",
visible: true,
type: 'formula',
func: (row) => {
row.timeExecutionStatus = (row.actualPromotionStartDate === row.plannedPromotionStartDate) && (row.actualPromotionEndDate === row.plannedPromotionEndDate) ? '是' : '否';
return row.timeExecutionStatus;
},
formulaStr: '公式:(实际档期开始时间 = 计划档期开始时间,并且实际档期结束时间 = 计划档期结束时间)',
fill: true,
width: 125
},
{
label: "计划促销机制",
prop: "plannedPromotionMechanism",
visible: true,
type: "string",
fill: false,
width: 115
},
{
label: "实际促销机制",
prop: "actualPromotionMechanism",
visible: true,
type: 'input',
referenceKey: "plannedPromotionMechanism",
fill: true,
width: 115,
render: (_, row, col) => {
return (
<div>
<span>{row[col.referenceKey] || '-'}</span>
<div>
<el-input modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
placeholder=""
clearable
/>
</div>
</div>
)
}
},
{
label: "促销机制是否执行",
prop: "promotionMechanismExecutionStatus",
visible: true,
type: 'select',
options: [
{
label: '是',
value: '是'
},
{
label: '否',
value: '否'
}
],
fill: true,
width: 135,
render: (_, row, col) => {
return (
<div>
<div style="width: 100%;">
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
placeholder=""
clearable
>
{col.options.map((item) => (
<el-option key={item.value} label={item.label} value={item.value} />
))}
</el-select>
</div>
</div>
)
}
},
{
label: "预估袋数",
prop: "estimatedBagCount",
visible: true,
type: "string",
fill: false,
width: 90
},
{
label: "档期备货量(袋)",
prop: "promotionStockQuantity",
visible: true,
type: 'input',
format: 'number',
referenceKey: "estimatedBagCount",
fill: true,
width: 135,
render: (_, row, col) => {
return (
<div>
<span>{row[col.referenceKey] || '-'}</span>
<div>
<el-input modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
placeholder=""
clearable
/>
</div>
</div>
)
}
},
{
label: "单包厂价(单包)",
prop: "unitFactoryPrice",
visible: true,
type: "string",
fill: false,
width: 135
},
{
label: "正常供价(单包)",
prop: "normalSupplyPrice",
visible: true,
type: "string",
fill: false,
width: 135
},
{
label: "日常零售价(单包)",
prop: "regularRetailPrice",
visible: true,
type: "string",
fill: false,
width: 150
},
{
label: "计划促销售价(单包)",
prop: "plannedPromotionPrice",
visible: true,
type: "string",
fill: false,
width: 165
},
{
label: "实际促销售价(单包)",
prop: "actualPromotionPrice",
visible: true,
type: 'input',
format: 'number',
referenceKey: "plannedPromotionPrice",
fill: true,
width: 165,
render: (_, row, col) => {
return (
<div>
<span>{row[col.referenceKey] || '-'}</span>
<div>
<el-input modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
placeholder=""
clearable
/>
</div>
</div>
)
},
requestKey: ["promotionPriceExecutionStatus"]
},
{
label: "促销价是否执行",
prop: "promotionPriceExecutionStatus",
visible: true,
type: 'formula',
func: (row) => {
row.promotionPriceExecutionStatus = row.plannedPromotionPrice == row.actualPromotionPrice ? '是' : '否';
return row.promotionPriceExecutionStatus;
},
formulaStr: '公式:(计划促销售价 = 实际促销售价)',
fill: true,
width: 140
},
{
label: "正常经销商毛利现状",
prop: "normalDealerMarginStatus",
visible: true,
type: "string",
fill: false,
width: 150
},
{
label: "系统前台毛利保证",
prop: "systemFrontendMarginGuarantee",
visible: true,
type: "string",
fill: false,
width: 135
},
{
label: "系统后台毛利保证",
prop: "systemBackendMarginGuarantee",
visible: true,
type: "string",
fill: false,
width: 135
},
{
label: "系统促销毛利保证",
prop: "systemPromotionMarginGuarantee",
visible: true,
type: "string",
fill: false,
width: 135
},
{
label: "经销商毛利保证",
prop: "dealerMarginGuarantee",
visible: true,
type: "string",
fill: false,
width: 125
},
{
label: "单袋补差金额(费用)",
prop: "unitBagAdjustmentAmount",
visible: true,
type: "string",
fill: false,
width: 165
},
{
label: "补差费比(%)",
prop: "adjustmentCostRatio",
visible: true,
type: "string",
fill: false,
width: 125
},
{
label: "整体补差(费用)",
prop: "totalAdjustmentCost",
visible: true,
type: "string",
fill: false,
width: 135
},
{
label: "档期是否执行",
prop: "promotionExecutionStatus",
visible: true,
type: 'formula',
func: (row) => {
// 促销价是否执行,促销机制是否执行,促销规格是否执行,档期是否开展
row.promotionExecutionStatus = row.promotionPriceExecutionStatus == '是' && row.promotionMechanismExecutionStatus == '是' && row.specExecutionStatus == '是' && row.promotionImplementationStatus == '是' ? '是' : '否';
return row.promotionExecutionStatus;
},
formulaStr: '公式:(促销价是否执行 = 是,并且促销机制是否执行 = 是,并且促销规格是否执行 = 是,并且档期是否开展 = 是)',
fill: true,
width: 125
}
],
visible: true
},
{
label: "海报计划",
prop: 'posterPlan',
children: [
{
label: "计划-海报形式",
prop: "plannedPosterFormat",
visible: true,
type: "string",
fill: false,
width: 115
},
{
label: "计划-海报费用",
prop: "plannedPosterCost",
visible: true,
type: "string",
fill: false,
width: 115
},
{
label: "实际-海报形式",
prop: "actualPosterFormat",
visible: true,
type: 'select',
options: [
{
label: '电子',
value: '电子'
},
{
label: '纸质',
value: '纸质'
}
],
referenceKey: "plannedPosterFormat",
fill: true,
width: 115,
render: (_, row, col) => {
return (
<div>
<span>{row[col.referenceKey] || '-'}</span>
<div style="width: 100%">
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
placeholder=""
clearable
>
{col.options.map(item => (
<el-option key={item.value} label={item.label} value={item.value} />
))}
</el-select>
</div>
</div>
)
},
requestKey: ["posterExecutionStatus"]
},
{
label: "海报是否执行",
prop: "posterExecutionStatus",
visible: true,
type: 'formula',
func: (row) => {
if (!row.actualPosterFormat) return row.posterExecutionStatus = '否'
row.posterExecutionStatus = row.plannedPosterFormat == row.actualPosterFormat ? '是' : '否';
return row.posterExecutionStatus;
},
formulaStr: '公式:(计划海报形式 = 实际海报形式)',
fill: true,
width: 125
}
],
visible: true
},
{
label: "档期陈列",
prop: 'displayPlan',
children: [
{
label: "门店数",
prop: "storeCountInt",
visible: true,
type: "string",
fill: false,
width: 75
},
{
label: "店均费用",
prop: "averageStoreCost",
visible: true,
type: "string",
fill: false,
width: 115
},
{
label: "费用",
prop: "cost",
visible: true,
type: "string",
fill: false,
width: 115
},
{
label: '修改人',
prop: 'updateBy',
visible: true,
type: 'string',
fill: false,
width: 120
},
{
label: '修改时间',
prop: 'updateTime',
visible: true,
type: 'string',
fill: false,
width: 140
}
],
visible: true
}
]);
// 表格列数据
const tableColumns = ref([])
// 右上角工具选择列
const chooseColumns = ref([])
<script setup>
import { getDisplayScheduleList, submitDisplaySchedulePlan } from '@/api'
import { parseTime } from '@/utils'
/*************** 操作类型 ***************/
const operation = ref('平铺模式');
const tableRef = ref(null)
// 平铺模式
const baseColumns = ref([
{
label: "基础信息",
children: [
{ label: '计划月份', prop: 'salesMonth', visible: true, type: 'string', fill: false, fixed: 'left' },
{ "label": "销售大区", "prop": "regionName", visible: true, type: "string", fill: false },
{ "label": "销售战区", "prop": "districtName", visible: true, type: "string", fill: false },
{ "label": "经销商-省份", "prop": "dealerProvince", visible: true, type: "string", fill: false },
{ "label": "经销商-城市", "prop": "dealerCity", visible: true, type: "string", fill: false },
{ "label": "经销商-代码", "prop": "dealerCode", visible: true, type: "string", fill: false },
{ "label": "经销商名称", "prop": "dealerName", visible: true, type: "string", fill: true, fixed: 'left' },
{ "label": "经销商类型", "prop": "dealerType", visible: true, type: "string", fill: false },
{ "label": "开户日期", "prop": "openingDate", visible: true, type: "string", fill: false },
{ "label": "闭户日期", "prop": "closingDate", visible: true, type: "string", fill: false },
{ "label": "大区总监", "prop": "regionManager", visible: true, type: "string", fill: false },
{ "label": "战区经理", "prop": "districtManager", visible: true, type: "string", fill: false },
{ "label": "城市经理", "prop": "cityManager", visible: true, type: "string", fill: false },
{ "label": "系统名称", "prop": "lineName", visible: true, type: "string", fill: true, fixed: 'left' },
{ "label": "系统类型", "prop": "lineType", visible: true, type: "string", fill: false },
{ "label": "渠道大类", "prop": "channelDl", visible: true, type: "string", fill: false },
{ "label": "渠道小类", "prop": "channelXl", visible: true, type: "string", fill: false },
{ "label": "系统业态", "prop": "lineLf", visible: true, type: "string", fill: false },
{ "label": "门店数", "prop": "storeCount", visible: true, type: "string", fill: false }
],
prop: 'baseColumns',
visible: true
},
{
label: "档期基础信息",
children: [
{ "label": "档期执行月份", "prop": "promotionExecutionMonth", visible: true, type: "string", fill: false },
{ "label": "档期计划-促销规格", "prop": "plannedPromotionSpec", visible: true, type: "string", fill: false },
{ "label": "档期计划-促销口味", "prop": "plannedPromotionFlavor", visible: true, type: "string", fill: false },
{
"label": "档期执行-促销规格",
"prop": "actualPromotionSpec",
visible: true,
type: 'select',
options: [
{ label: '虎皮105g', value: '虎皮105g' },
{ label: '虎皮210g', value: '虎皮210g' },
{ label: '去骨72g', value: '去骨72g' },
{ label: '去骨138g', value: '去骨138g' },
{ label: '小鸡腿80g', value: '小鸡腿80g' },
{ label: '老卤95g', value: '老卤95g' },
{ label: '鸡肉豆堡', value: '鸡肉豆堡' },
{ label: '牛肉豆堡', value: '牛肉豆堡' },
],
referenceKey: "plannedPromotionSpec",
fill: true
},
{
"label": "档期执行-促销口味",
"prop": "actualPromotionFlavor",
visible: true,
type: 'input',
referenceKey: "plannedPromotionFlavor",
fill: true
},
{
"label": "档期规格是否执行",
"prop": "specExecutionStatus",
visible: true,
type: 'formula',
func: (row) => {
row.specExecutionStatus = row.plannedPromotionSpec === row.actualPromotionSpec ? '是' : '否';
return row.specExecutionStatus;
},
formulaStr: '公式:(档期计划促销规格 = 档期执行促销规格)',
fill: true
},
],
prop: 'scheduleInfo',
visible: true
},
{
label: "合计费用",
prop: 'totalCostParent',
children: [
{ "label": "合计费用", "prop": "totalCost", visible: true, type: "string", fill: false },
{ "label": "合计费率", "prop": "totalCostRate", visible: true, type: "string", fill: false },
],
visible: true
},
{
label: "促销计划",
prop: 'promotionPlan',
children: [
{ "label": "计划-档期形式", "prop": "plannedPromotionFormat", visible: true, type: "string", fill: false },
{ "label": "计划-核销方式", "prop": "plannedVerificationMethod", visible: true, type: "string", fill: false },
{ "label": "计划-档期规则", "prop": "plannedPromotionRules", visible: true, type: "string", fill: false },
{ "label": "计划-档期前补差天数", "prop": "plannedPrePromotionAdjustDays", visible: true, type: "string", fill: false },
{ "label": "计划-档期后补差天数", "prop": "plannedPostPromotionAdjustDays", visible: true, type: "string", fill: false },
{
"label": "计划-档期开始时间",
"prop": "plannedPromotionStartDate",
visible: true,
type: "string",
fill: false
},
{
"label": "实际-档期开始时间",
"prop": "actualPromotionStartDate",
visible: true,
type: 'date',
format: 'MM-DD',
referenceKey: "plannedPromotionStartDate",
fill: true
},
{ "label": "计划-档期结束时间", "prop": "plannedPromotionEndDate", visible: true, type: "string", fill: false },
{
"label": "实际-档期结束时间",
"prop": "actualPromotionEndDate",
visible: true,
type: 'date',
format: 'MM-DD',
fill: true,
referenceKey: "plannedPromotionEndDate",
},
{
"label": "档期是否开展",
"prop": "promotionImplementationStatus",
visible: true,
type: 'formula',
func: (row) => {
row.promotionImplementationStatus = row.actualPromotionStartDate && row.actualPromotionEndDate ? '是' : '否';
return row.promotionImplementationStatus;
},
formulaStr: '公式:(实际档期开始时间不为空,并且实际档期结束时间不为空)',
fill: true
},
{
"label": "时间是否执行",
"prop": "timeExecutionStatus",
visible: true,
type: 'formula',
func: (row) => {
row.timeExecutionStatus = (row.actualPromotionStartDate === row.plannedPromotionStartDate) && (row.actualPromotionEndDate === row.plannedPromotionEndDate) ? '是' : '否';
return row.timeExecutionStatus;
},
formulaStr: '公式:(实际档期开始时间 = 计划档期开始时间,并且实际档期结束时间 = 计划档期结束时间)',
fill: true
},
{ "label": "计划-补差开始时间", "prop": "plannedAdjustmentStartDate", visible: true, type: "string", fill: false },
{ "label": "计划-补差结束时间", "prop": "plannedAdjustmentEndDate", visible: true, type: "string", fill: false },
{ "label": "计划促销机制", "prop": "plannedPromotionMechanism", visible: true, type: "string", fill: false },
{
"label": "实际促销机制",
"prop": "actualPromotionMechanism",
visible: true,
type: 'input',
fill: true
},
{
"label": "促销机制是否执行",
"prop": "promotionMechanismExecutionStatus",
visible: true,
type: 'select',
options: [
{ label: '是', value: '是' },
{ label: '否', value: '否' },
],
fill: true
},
{ "label": "预估袋数", "prop": "estimatedBagCount", visible: true, type: "string", fill: false },
{
"label": "档期备货量(袋)",
"prop": "promotionStockQuantity",
visible: true,
type: 'input',
format: 'number',
fill: true
},
{ "label": "单包厂价(单包)", "prop": "unitFactoryPrice", visible: true, type: "string", fill: false },
{ "label": "正常供价(单包)", "prop": "normalSupplyPrice", visible: true, type: "string", fill: false },
{ "label": "日常零售价(单包)", "prop": "regularRetailPrice", visible: true, type: "string", fill: false },
{ "label": "计划促销售价(单包)", "prop": "plannedPromotionPrice", visible: true, type: "string", fill: false },
{
"label": "实际促销售价(单包)",
"prop": "actualPromotionPrice",
visible: true,
type: 'input',
format: 'number',
fill: true
},
{
"label": "促销价是否执行",
"prop": "promotionPriceExecutionStatus", visible: true,
type: 'formula',
func: (row) => {
row.promotionPriceExecutionStatus = row.plannedPromotionPrice == row.actualPromotionPrice ? '是' : '否';
return row.promotionPriceExecutionStatus;
},
formulaStr: '公式:(计划促销售价 = 实际促销售价)',
fill: true
},
{ "label": "正常经销商毛利现状", "prop": "normalDealerMarginStatus", visible: true, type: "string", fill: false },
{ "label": "系统前台毛利保证", "prop": "systemFrontendMarginGuarantee", visible: true, type: "string", fill: false },
{ "label": "系统后台毛利保证", "prop": "systemBackendMarginGuarantee", visible: true, type: "string", fill: false },
{ "label": "系统促销毛利保证", "prop": "systemPromotionMarginGuarantee", visible: true, type: "string", fill: false },
{ "label": "经销商毛利保证", "prop": "dealerMarginGuarantee", visible: true, type: "string", fill: false },
{ "label": "单袋补差金额(费用)", "prop": "unitBagAdjustmentAmount", visible: true, type: "string", fill: false },
{ "label": "补差费比(%)", "prop": "adjustmentCostRatio", visible: true, type: "string", fill: false },
{ "label": "整体补差(费用)", "prop": "totalAdjustmentCost", visible: true, type: "string", fill: false },
{
"label": "档期是否执行",
"prop": "promotionExecutionStatus",
visible: true,
type: 'formula',
func: (row) => {
// 促销价是否执行,促销机制是否执行,促销规格是否执行,档期是否开展
row.promotionExecutionStatus = row.promotionPriceExecutionStatus == '是' && row.promotionMechanismExecutionStatus == '是' && row.specExecutionStatus == '是' && row.promotionImplementationStatus == '是' ? '是' : '否';
return row.promotionExecutionStatus;
},
formulaStr: '公式:(促销价是否执行 = 是,并且促销机制是否执行 = 是,并且促销规格是否执行 = 是,并且档期是否开展 = 是)',
fill: true
},
],
visible: true
}, {
label: "海报计划",
prop: 'posterPlan',
children: [
{ "label": "计划-海报形式", "prop": "plannedPosterFormat", visible: true, type: "string", fill: false },
{ "label": "计划-海报费用", "prop": "plannedPosterCost", visible: true, type: "string", fill: false },
{
"label": "实际-海报形式",
"prop": "actualPosterFormat",
visible: true,
type: 'select',
options: [
{ label: '电子', value: '电子' },
{ label: '纸制', value: '纸制' }
],
fill: true
},
{
"label": "海报是否执行",
"prop": "posterExecutionStatus",
visible: true,
type: 'formula',
func: (row) => {
row.posterExecutionStatus = row.plannedPosterFormat == row.actualPosterFormat ? '是' : '否';
return row.posterExecutionStatus;
},
formulaStr: '公式:(计划海报形式 = 实际海报形式)',
fill: true
},
],
visible: true
},
{
label: "档期陈列",
prop: 'displayPlan',
children: [
{ "label": "门店数", "prop": "storeCountInt", visible: true, type: "string", fill: false },
{ "label": "店均费用", "prop": "averageStoreCost", visible: true, type: "string", fill: false },
{ "label": "费用", "prop": "cost", visible: true, type: "string", fill: false },
],
visible: true
// 计算列具体数据
const updateColumns = (operation) => {
// 保存用户选择的列配置
if (operation === '平铺模式') {
chooseColumns.value = baseColumns.value
tableColumns.value = baseColumns.value.flatMap(item => {
if (item.children) {
return item.children.filter(child => child);
}
return [];
});
} else if (operation === '填报模式') {
chooseColumns.value = baseColumns.value.flatMap(item => {
if (item.children) {
return item.children
.filter(child => child.fill)
}
return [];
});
tableColumns.value = chooseColumns.value
}
}
]);
// 表格列数据
const tableColumns = ref([])
// 右上角工具选择列
const chooseColumns = ref([])
// 计算列具体数据
const checkTableColumns = () => {
if (operation.value === '平铺模式') {
chooseColumns.value = baseColumns.value
tableColumns.value = baseColumns.value.flatMap(item => {
if (item.children) {
return item.children.filter(child => child);
}
return [];
});
} else if (operation.value === '填报模式') {
chooseColumns.value = baseColumns.value.flatMap(item => {
// // 选择列默认选中哪些
const visibleProps = computed(() => {
// 分情况返回
return tableColumns.value.flatMap(item => {
if (item.children) {
return item.children
.filter(child => child.fill)
return item.children.map(child => child.prop);
}
return [];
return item.prop;
});
tableColumns.value = chooseColumns.value
}
});
// 强制表格立即应用所有宽度设置,避免先自适应再调整
nextTick(() => {
if (tableRef.value) {
tableRef.value.doLayout()
}
})
}
onMounted(() => {
checkTableColumns()
})
// 选择列默认选中哪些
const visibleProps = computed(() => {
// 分情况返回
return tableColumns.value.flatMap(item => {
if (item.children) {
return item.children.map(child => child.prop);
// 提交变更
const submitChange = async (row, col) => {
// 需要特殊处理的
// 1. 实际主货架-形式,为空时,置空实际主货架-数量
if (col.prop === 'actualMainShelfType' && !row.actualMainShelfType) {
row.actualMainShelfQty = 0
}
return item.prop;
});
});
// 提交变更
const submitChange = async (row, col) => {
// 如果是 实际促销售价(单包),保留 2 位小数
if (col.prop === 'actualPromotionPrice') {
row[col.prop] = parseFloat(parseFloat(row[col.prop]).toFixed(2))
}
let requestObj = {}
if (col.requestKey) {
// 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) {
const obj = tableColumns.value.find(item => item.prop == str)
if (obj.type === 'formula') {
obj.func(row)
}
}
requestObj = col.requestKey.reduce((acc, key) => ({ ...acc, [key]: row[key] }), {})
}
await submitDisplaySchedulePlan({
id: row.sapId,
[col.prop]: row[col.prop]
})
// 这些提交变化的,同时会影响公式计算的,需要把公式计算的字段列结果,一起发给后台
// 档期规格是否执行:计划规格和执行规格
if (['plannedPromotionSpec', 'actualPromotionSpec'].includes(col.prop)) {
await submitDisplaySchedulePlan({
id: row.sapId,
specExecutionStatus: row.specExecutionStatus,
[col.prop]: row[col.prop], // 当前修改列的值
...requestObj
})
}
// 档期是否开展:实际档期开始和结束
if (['actualPromotionStartDate', 'actualPromotionEndDate'].includes(col.prop)) {
await submitDisplaySchedulePlan({
id: row.sapId,
promotionImplementationStatus: row.promotionImplementationStatus,
})
}
// 档期是否执行:实际开始和计划开始时间,实际结束时间和计划结束时间
if (['actualPromotionStartDate', 'plannedPromotionStartDate', 'actualPromotionEndDate', 'plannedPromotionEndDate'].includes(col.prop)) {
await submitDisplaySchedulePlan({
id: row.sapId,
timeExecutionStatus: row.timeExecutionStatus,
})
}
// 促销价是否执行:根据计划和实际促销价
if (['actualPromotionPrice', 'plannedPromotionPrice'].includes(col.prop)) {
await submitDisplaySchedulePlan({
id: row.sapId,
promotionPriceExecutionStatus: row.promotionPriceExecutionStatus,
// // 表格数据
const tableData = ref([])
const isLoading = ref(true)
const params = reactive({
pageNum: 1,
pageSize: 20,
})
const total = ref(0)
// // 获取表格数据
const getTableList = async () => {
isLoading.value = true
const res = await getDisplayScheduleList({
...params
})
}
// 海报是否执行
if (['plannedPosterFormat', 'actualPosterFormat'].includes(col.prop)) {
await submitDisplaySchedulePlan({
id: row.sapId,
posterExecutionStatus: row.posterExecutionStatus,
// 处理日期格式
res.data.rows.forEach(item => {
item.salesMonth = parseTime(item.salesMonth, '{y}-{m}')
item.openingDate = parseTime(item.openingDate, '{y}-{m}-{d}')
item.plannedAdjustmentStartDate = parseTime(item.plannedAdjustmentStartDate, '{y}-{m}-{d}')
item.plannedAdjustmentEndDate = parseTime(item.plannedAdjustmentEndDate, '{y}-{m}-{d}')
item.plannedPromotionStartDate = parseTime(item.plannedPromotionStartDate, '{y}-{m}-{d}')
item.actualPromotionStartDate = parseTime(item.actualPromotionStartDate, '{y}-{m}-{d}')
item.plannedPromotionEndDate = parseTime(item.plannedPromotionEndDate, '{y}-{m}-{d}')
item.actualPromotionEndDate = parseTime(item.actualPromotionEndDate, '{y}-{m}-{d}')
item.totalCostRate = item.totalCostRate ? `${(item.totalCostRate).toFixed(2)}%` : '-'
item.adjustmentCostRatio = item.adjustmentCostRatio ? `${(item.adjustmentCostRatio).toFixed(2)}%` : '-'
})
tableData.value = res.data.rows
total.value = res.data.total
isLoading.value = false
}
// 档期计划是否执行
await submitDisplaySchedulePlan({
id: row.sapId,
promotionExecutionStatus: row.promotionExecutionStatus,
})
}
// 表格数据
const tableData = ref([])
const isLoading = ref(true)
const params = reactive({
pageNum: 1,
pageSize: 10,
})
const total = ref(0)
// 筛选工具
const showSearch = ref(true);
const getTableList = async () => {
isLoading.value = true
const res = await getDisplayScheduleList({
...params
})
// 处理日期格式
res.data.rows.forEach(item => {
item.salesMonth = parseTime(item.salesMonth, '{y}-{m}')
item.openingDate = parseTime(item.openingDate, '{y}-{m}-{d}')
item.plannedAdjustmentStartDate = parseTime(item.plannedAdjustmentStartDate, '{y}-{m}-{d}')
item.plannedAdjustmentEndDate = parseTime(item.plannedAdjustmentEndDate, '{y}-{m}-{d}')
item.plannedPromotionStartDate = parseTime(item.plannedPromotionStartDate, '{y}-{m}-{d}')
item.actualPromotionStartDate = parseTime(item.actualPromotionStartDate, '{y}-{m}-{d}')
item.plannedPromotionEndDate = parseTime(item.plannedPromotionEndDate, '{y}-{m}-{d}')
item.actualPromotionEndDate = parseTime(item.actualPromotionEndDate, '{y}-{m}-{d}')
item.totalCostRate = item.totalCostRate ? `${(item.totalCostRate).toFixed(2)}%` : '-'
item.adjustmentCostRatio = item.adjustmentCostRatio ? `${(item.adjustmentCostRatio).toFixed(2)}%` : '-'
})
tableData.value = res.data.rows
total.value = res.data.total
isLoading.value = false
}
getTableList()
// 列宽度
const getColumnMinWidth = (column) => {
// 根据列名或属性判断合适的最小宽度
const widthMap = {
'salesMonth': 85,
'regionName': 85,
'districtName': 85,
'dealerProvince': 100,
'dealerCity': 100,
'dealerCode': 100,
'dealerName': 210,
'dealerType': 110,
'openingDate': 100,
'closingDate': 100,
'regionManager': 85,
'districtManager': 85,
'cityManager': 85,
'lineName': 85,
'lineType': 85,
'channelDl': 85,
'channelXl': 135,
'lineLf': 85,
'storeCount': 70,
'promotionExecutionMonth': 105,
'plannedPromotionSpec': 135,
'plannedPromotionFlavor': 135,
'actualPromotionSpec': 135,
'actualPromotionFlavor': 135,
'specExecutionStatus': 145,
'totalCost': 100,
'totalCostRate': 80,
'plannedPromotionFormat': 110,
'plannedVerificationMethod': 110,
'plannedPromotionRules': 110,
'plannedPrePromotionAdjustDays': 150,
'plannedPostPromotionAdjustDays': 150,
'plannedPromotionStartDate': 135,
'actualPromotionStartDate': 135,
'plannedPromotionEndDate': 135,
'actualPromotionEndDate': 135,
'promotionImplementationStatus': 120,
'timeExecutionStatus': 120,
'plannedAdjustmentStartDate': 135,
'plannedAdjustmentEndDate': 135,
'plannedPromotionMechanism': 110,
'actualPromotionMechanism': 110,
'promotionMechanismExecutionStatus': 130,
'estimatedBagCount': 85,
'promotionStockQuantity': 130,
'unitFactoryPrice': 130,
'normalSupplyPrice': 130,
'regularRetailPrice': 145,
'plannedPromotionPrice': 160,
'actualPromotionPrice': 160,
'promotionPriceExecutionStatus': 135,
'normalDealerMarginStatus': 145,
'systemFrontendMarginGuarantee': 130,
'systemBackendMarginGuarantee': 130,
'systemPromotionMarginGuarantee': 130,
'dealerMarginGuarantee': 120,
'unitBagAdjustmentAmount': 160,
'adjustmentCostRatio': 120,
'totalAdjustmentCost': 130,
'promotionExecutionStatus': 120,
'plannedPosterFormat': 110,
'plannedPosterCost': 110,
'actualPosterFormat': 110,
'posterExecutionStatus': 120,
'storeCountInt': 70,
'averageStoreCost': 110,
'cost': 110
};
return widthMap[column.prop] || 150; // 默认宽度
};
onMounted(() => {
updateColumns()
getTableList()
})
const isContentOverflow = (key, content) => {
// 创建临时元素用于测量
if (!content) return false;
const isContentOverflow = (key, content) => {
// 创建临时元素用于测量
if (!content) return false;
const tempSpan = document.createElement('span');
tempSpan.textContent = content;
tempSpan.className = 'ellipsis-tooltip';
tempSpan.style.visibility = 'hidden';
tempSpan.style.position = 'absolute';
document.body.appendChild(tempSpan);
const tempSpan = document.createElement('span');
tempSpan.textContent = content;
tempSpan.className = 'ellipsis-tooltip';
tempSpan.style.visibility = 'hidden';
tempSpan.style.position = 'absolute';
document.body.appendChild(tempSpan);
// 获取元素宽度和内容宽度
const { width } = tempSpan.getBoundingClientRect();
const scrollWidth = tempSpan.scrollWidth;
// 获取元素宽度和内容宽度
const { width } = tempSpan.getBoundingClientRect();
const scrollWidth = tempSpan.scrollWidth;
// 移除临时元素
document.body.removeChild(tempSpan);
// 移除临时元素
document.body.removeChild(tempSpan);
// 当内容宽度大于元素宽度时,表示内容溢出
return scrollWidth > width;
};
// 当内容宽度大于元素宽度时,表示内容溢出
return scrollWidth > width;
};
</script>
<style scoped
lang="scss">
.container {
.el-tabs {
height: 100%;
display: flex;
flex-direction: column-reverse;
.el-tabs__content {
display: flex;
flex-direction: column;
height: 100%;
.el-tab-pane {
height: 100%;
display: flex;
flex-direction: column;
.pagination-container {
margin: 10px;
}
}
.formula-column {
display: flex;
justify-content: center;
align-items: center;
/* 保留原有样式,但简化了结构 */
.formula-column {
display: flex;
justify-content: center;
align-items: center;
.el-icon {
margin-left: 2px;
}
}
}
.el-icon {
margin-left: 2px;
}
}
.el-form-item {
align-items: center;
::v-deep(.el-table) {
.cell {
padding: 0;
}
/* 表格区域 */
.auto-fit-header-table {
.cell-style {
margin: 0 -12px;
/* 优化超长文本的显示效果 */
.cell {
/* padding: 0 .2133rem; */
}
>div {
display: flex;
flex-direction: column;
align-items: flex-start;
::v-deep(.el-table__row) {
.el-table__cell {
padding: 0;
>span.ellipsis-tooltip {
text-align: left;
text-indent: 5px;
display: inline-block;
width: 100%;
background-color: #e1e2e6;
border-bottom: 1px solid #ebeef5;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
max-width: 100%;
}
}
/* 表格内下拉框 */
.el-select {
width: 100% !important;
padding: 10px;
}
::v-deep(.column-style) {
.cell-style {
margin: 0 -12px;
>div {
display: flex;
flex-direction: column;
align-items: flex-start;
>span.ellipsis-tooltip {
text-align: left;
text-indent: 5px;
display: inline-block;
width: 100%;
background-color: #e1e2e6;
border-bottom: 1px solid #ebeef5;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
max-width: 100%;
}
}
/* 表格内下拉框 */
.el-select {
width: 100% !important;
padding: 10px;
}
.el-input {
padding: 10px;
}
.el-input {
padding: 10px;
}
.date-picker {
padding: 10px;
.date-picker {
padding: 10px;
.el-input {
padding: 0;
}
}
.el-input {
padding: 0;
}
}
}
}
::v-deep(.el-date-editor) {
......
<!-- 档期陈列 -->
<template>
<CommonPlan :tableData="tableData"
:isLoading="isLoading"
:total="total"
:tableColumns="tableColumns"
:chooseColumns="chooseColumns"
:visibleProps="visibleProps"
:params="params"
@updateColumns="updateColumns"
@getTableList="getTableList" />
</template>
<script setup
lang="jsx">
import CommonPlan from '@/views/promotion/components/CommonPlan'
import { getDisplayScheduleDetail, submitDisplayScheduleDetail } from '@/api'
import { parseTime } from '@/utils'
import { onMounted } from 'vue';
/*************** 操作类型 ***************/
// 全部列
const baseColumns = ref([
// 一级类
{
label: "基础信息", // 类型标题
children: [
{
"label": "销售大区",
"prop": "regionName",
"visible": true,
"type": "string",
"fill": false,
"width": 90
},
{
"label": "销售战区",
"prop": "districtName",
"visible": true,
"type": "string",
"fill": false,
"width": 90
},
{
"label": "经销商 - 省份",
"prop": "dealerProvince",
"visible": true,
"type": "string",
"fill": false,
"width": 110
},
{
"label": "经销商 - 城市",
"prop": "dealerCity",
"visible": true,
"type": "string",
"fill": false,
"width": 110
},
{
"label": "经销商 - 代码",
"prop": "dealerCode",
"visible": true,
"type": "string",
"fill": false,
"width": 110
},
{
"label": "经销商名称",
"prop": "dealerName",
"visible": true,
"type": "string",
"fill": false,
"width": 200
},
{
"label": "经销商类型",
"prop": "dealerType",
"visible": true,
"type": "string",
"fill": false,
"width": 150
},
{
"label": "开户日期",
"prop": "openingDate",
"visible": true,
"type": "string",
"fill": false,
"width": 120
},
{
"label": "闭户日期",
"prop": "closingDate",
"visible": true,
"type": "string",
"fill": false,
"width": 120
},
{
"label": "大区总监",
"prop": "regionManager",
"visible": true,
"type": "string",
"fill": false,
"width": 80
},
{
"label": "战区经理",
"prop": "districtManager",
"visible": true,
"type": "string",
"fill": false,
"width": 80
},
{
"label": "城市经理",
"prop": "cityManager",
"visible": true,
"type": "string",
"fill": false,
"width": 80
},
{
"label": "系统名称",
"prop": "lineName",
"visible": true,
"type": "string",
"fill": false,
"width": 100
},
{
"label": "系统类型",
"prop": "lineType",
"visible": true,
"type": "string",
"fill": false,
"width": 110
},
{
"label": "渠道大类",
"prop": "channelDl",
"visible": true,
"type": "string",
"fill": false,
"width": 100
},
{
"label": "渠道小类",
"prop": "channelXl",
"visible": true,
"type": "string",
"fill": false,
"width": 150
},
{
"label": "系统业态",
"prop": "lineLf",
"visible": true,
"type": "string",
"fill": false,
"width": 120
},
{
"label": "门店数",
"prop": "storeCount",
"visible": true,
"type": "string",
"fill": false,
"width": 90
}
],
prop: 'baseColumns',
visible: true
},
{
label: "档期基础信息",
children: [
{
"label": "档期执行月份",
"prop": "promotionExecutionMonth",
"visible": true,
"type": "string",
"fill": false,
"width": 120
},
{
"label": "档期计划 - 促销规格",
"prop": "plannedPromotionSpec",
"visible": true,
"type": "string",
"fill": false,
"width": 160
},
{
"label": "档期计划 - 促销口味",
"prop": "plannedPromotionFlavor",
"visible": true,
"type": "string",
"fill": false,
"width": 160
}
],
prop: 'scheduleBaseColumns',
visible: true
},
{
label: "促销计划",
prop: 'scheduleFpColumns',
children: [
{
"label": "计划 - 档期规则",
"prop": "plannedPromotionRules",
"visible": true,
"type": "string",
"fill": false,
"width": 140
},
{
"label": "计划 - 档期前补差天数",
"prop": "plannedPrePromotionAdjustDays",
"visible": true,
"type": "string",
"fill": false,
"width": 160
},
{
"label": "计划 - 档期后补差天数",
"prop": "plannedPostPromotionAdjustDays",
"visible": true,
"type": "string",
"fill": false,
"width": 160
},
{
"label": "计划 - 档期开始时间",
"prop": "plannedPromotionStartDate",
"visible": true,
"type": "string",
"fill": false,
"width": 160
},
{
"label": "计划 - 补差开始时间",
"prop": "plannedAdjustmentStartDate",
"visible": true,
"type": "string",
"fill": false,
"width": 160
},
{
"label": "计划 - 档期结束时间",
"prop": "plannedPromotionEndDate",
"visible": true,
"type": "string",
"fill": false,
"width": 160
},
{
"label": "计划 - 补差结束时间",
"prop": "plannedAdjustmentEndDate",
"visible": true,
"type": "string",
"fill": false,
"width": 160
}
],
visible: true
},
{
label: '档期门店陈列计划',
prop: 'scheduleStoreColumns',
children: [
{
"label": "计划 - 档期陈列开始时间",
"prop": "plannedPromotionDisplayStartDate",
"visible": true,
"type": "string",
"fill": false,
"width": 180
},
{
"label": "计划 - 档期陈列结束时间",
"prop": "plannedPromotionDisplayEndDate",
"visible": true,
"type": "string",
"fill": false,
"width": 180
},
{
"label": "门店编码",
"prop": "storeCode",
"visible": true,
"type": "string",
"fill": false,
"width": 120
},
{
"label": "门店名称",
"prop": "storeName",
"visible": true,
"type": "string",
"fill": false,
"width": 170
},
{
"label": "门店类型",
"prop": "storeType",
"visible": true,
"type": "string",
"fill": false,
"width": 80
},
{
"label": "门店计数",
"prop": "storeCountNum",
"visible": true,
"type": "string",
"fill": false,
"width": 80
},
{
"label": "门店地址",
"prop": "storeAddress",
"visible": true,
"type": "string",
"fill": false,
"width": 280
},
{
"label": "计划端架 - 数量",
"prop": "plannedEndCapQty",
"visible": true,
"type": "string",
"fill": false,
"width": 140
},
{
"label": "计划端架 - 费用",
"prop": "plannedEndCapTotalCost",
"visible": true,
"type": "string",
"fill": false,
"width": 140
},
{
"label": "实际端架 - 数量",
"prop": "actualEndCapQty",
"visible": true,
"type": "select",
referenceKey: "plannedEndCapQty",
"options": [
{
"label": "0",
"value": 0
},
{
"label": "0.5",
"value": 0.5
},
{
"label": "1",
"value": 1
},
{
"label": "2",
"value": 2
}
],
render: (_, row, col) => {
return (
<div>
<span>{row[col.referenceKey] || '-'}</span>
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
placeholder="">
{col.options.map(item => (
<el-option
key={item.value}
label={item.label}
value={item.value}
/>
))}
</el-select>
</div>
)
},
"fill": true,
"width": 140,
requestKey: ["actualEndCapExecuted"]
},
{
"label": "实际端架 - 是否执行",
"prop": "actualEndCapExecuted",
"visible": true,
"type": "formula",
"fill": true,
"width": 160,
func: (row) => {
row.actualEndCapExecuted = row.actualEndCapQty >= row.plannedEndCapQty ? '是' : '否'
return row.actualEndCapExecuted
},
formulaStr: "执行端架数量 >= 计划端架数量"
},
{
"label": "计划地堆 - 平米数(㎡)",
"prop": "plannedFloorStackArea",
"visible": true,
"type": "string",
"fill": false,
"width": 180
},
{
"label": "计划地堆 - 数量",
"prop": "plannedFloorStackQty",
"visible": true,
"type": "string",
"fill": false,
"width": 140
},
{
"label": "计划地堆 - 费用",
"prop": "plannedFloorStackTotalCost",
"visible": true,
"type": "string",
"fill": false,
"width": 140
},
{
"label": "实际地堆 - 平米数(㎡)",
"prop": "actualFloorStackArea",
"visible": true,
"type": "string",
"fill": false,
"width": 180
},
{
"label": "实际地堆 - 数量",
"prop": "actualFloorStackQty",
"visible": true,
"type": "string",
"fill": false,
"width": 140
},
{
"label": "实际地堆是否执行",
"prop": "actualFloorStackExecuted",
"visible": true,
"type": "string",
"fill": false,
"width": 140
},
{
"label": "计划其他陈列数量 + 形式",
"prop": "plannedOtherDisplay",
"visible": true,
"type": "string",
"fill": false,
"width": 180
},
{
"label": "计划其他陈列 - 总费用",
"prop": "plannedOtherDisplayTotalCost",
"visible": true,
"type": "string",
"fill": false,
"width": 160
},
{
"label": "实际其他陈列 - 数量 + 形式",
"prop": "actualOtherDisplay",
"visible": true,
"type": "string",
"fill": false,
"width": 190
},
{
"label": "实际其他陈列 - 是否执行",
"prop": "actualOtherDisplayExecuted",
"visible": true,
"type": "string",
"fill": false,
"width": 170
},
{
"label": "合计费用 - 费用",
"prop": "totalCost",
"visible": true,
"type": "string",
"fill": false,
"width": 140
},
{
"label": "档期陈列是否执行",
"prop": "promotionDisplayExecuted",
"visible": true,
"type": "string",
"fill": false,
"width": 140
},
{
"label": "付费陈列 - 是否",
"prop": "paidDisplay",
"visible": true,
"type": "string",
"fill": false,
"width": 120
},
{
"label": "当月拜访目标 - 是否",
"prop": "monthlyVisitTarget",
"visible": true,
"type": "string",
"fill": false,
"width": 160
}
],
visible: true
}
]);
// 表格列
const tableColumns = ref([])
// 右上角工具选择列
const chooseColumns = ref([])
// 计算列具体数据
const updateColumns = (operation) => {
return new Promise((resolve, reject) => {
if (operation === '平铺模式') {
chooseColumns.value = baseColumns.value
tableColumns.value = baseColumns.value.flatMap(item => {
if (item.children) {
return item.children.filter(child => !child.onlyFill);
}
return [];
});
} else if (operation === '填报模式') {
chooseColumns.value = baseColumns.value.flatMap(item => {
if (item.children) {
return item.children.filter(child => child.fill === true)
}
return [];
});
tableColumns.value = chooseColumns.value
}
resolve()
})
}
onMounted(() => {
updateColumns()
})
// 树形结构选择列默认有哪些
const visibleProps = computed(() => {
return baseColumns.value.flatMap(item => {
if (item.children) {
return item.children.filter(child => child.visible).map(child => child.prop);
}
return item.prop;
});
});
// 提交变更
const submitChange = async (row, col) => {
// 需要特殊处理的
// 1. 实际主货架-形式,为空时,置空实际主货架-数量
if (col.prop === 'actualMainShelfType' && !row.actualMainShelfType) {
row.actualMainShelfQty = 0
}
// 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) {
const obj = tableColumns.value.find(item => item.prop === str)
console.log('obj', obj)
if (obj.type === 'formula') {
obj.func(row)
}
}
await submitDisplayScheduleDetail({
id: row.sapdId,
[col.prop]: row[col.prop], // 当前修改列的值
...col.requestKey.reduce((acc, key) => ({ ...acc, [key]: row[key] }), {}), // 额外携带影响的列字段值
})
}
// 表格数据
const tableData = ref([])
const isLoading = ref(true)
const params = reactive({
pageNum: 1,
pageSize: 20,
})
const total = ref(0)
// 筛选工具
const getTableList = async () => {
isLoading.value = true
const res = await getDisplayScheduleDetail({
...params
})
res.data.rows.forEach(item => {
// 计划月份
item.promotionExecutionMonth = parseTime(item.promotionExecutionMonth, '{y}-{m}-{d}')
// 开户日期
item.openingDate = parseTime(item.openingDate, '{y}-{m}-{d}')
// 闭户日期
item.closingDate = parseTime(item.closingDate, '{y}-{m}-{d}')
// 计划 - 档期开始时间
item.plannedPromotionStartDate = parseTime(item.plannedPromotionStartDate, '{y}-{m}-{d}')
// 计划 - 补差开始时间
item.plannedAdjustmentStartDate = parseTime(item.plannedAdjustmentStartDate, '{y}-{m}-{d}')
// 计划 - 档期结束时间
item.plannedPromotionEndDate = parseTime(item.plannedPromotionEndDate, '{y}-{m}-{d}')
// 计划 - 补差结束时间
item.plannedAdjustmentEndDate = parseTime(item.plannedAdjustmentEndDate, '{y}-{m}-{d}')
// 计划 - 档期陈列开始时间
item.plannedPromotionDisplayStartDate = parseTime(item.plannedPromotionDisplayStartDate, '{y}-{m}-{d}')
// 计划 - 档期陈列结束时间
item.plannedPromotionDisplayEndDate = parseTime(item.plannedPromotionDisplayEndDate, '{y}-{m}-{d}')
})
tableData.value = res.data.rows
total.value = res.data.total
isLoading.value = false
}
getTableList()
</script>
<style scoped
lang="scss">
.container {
.el-tabs {
height: 100%;
display: flex;
flex-direction: column-reverse;
.el-tabs__content {
display: flex;
flex-direction: column;
height: 100%;
.el-tab-pane {
height: 100%;
display: flex;
flex-direction: column;
.pagination-container {
margin: 10px;
}
}
.formula-column {
display: flex;
justify-content: center;
align-items: center;
.el-icon {
margin-left: 2px;
}
}
}
}
.el-form-item {
align-items: center;
}
/* 表格区域 */
.auto-fit-header-table {
/* 优化超长文本的显示效果 */
.cell {
/* padding: 0 .2133rem; */
}
::v-deep(.el-table__row) {
.el-table__cell {
padding: 0;
}
}
::v-deep(.column-style) {
.cell-style {
margin: 0 -12px;
>div {
display: flex;
flex-direction: column;
align-items: flex-start;
>span {
text-align: left;
text-indent: 5px;
display: inline-block;
width: 100%;
background-color: #e1e2e6;
border-bottom: 1px solid #ebeef5;
}
}
/* 表格内下拉框 */
.el-select {
width: 100% !important;
padding: 10px;
}
.el-input {
padding: 10px;
}
}
}
}
}
</style>
\ No newline at end of file
<template>
<CommonPlan :tableData="tableData"
:isLoading="isLoading"
:total="total"
:tableColumns="tableColumns"
:chooseColumns="chooseColumns"
:visibleProps="visibleProps"
:params="params"
@updateColumns="updateColumns"
@getTableList="getTableList" />
</template>
<script setup
lang="jsx">
import CommonPlan from '@/views/promotion/components/CommonPlan'
import { getSnackPlanList, submitDisplayPlan } from '@/api'
import { parseTime } from '@/utils'
import { onMounted } from 'vue';
/*************** 操作类型 ***************/
// 全部列
const baseColumns = ref([
// 一级类
{
label: "基础信息",
children: [
{
"label": "销售大区",
"prop": "regionName",
"visible": true,
"type": "string",
"fill": false,
"width": 100
},
{
"label": "销售战区",
"prop": "districtName",
"visible": true,
"type": "string",
"fill": false,
"width": 100
},
{
"label": "经销商 - 省份",
"prop": "dealerProvince",
"visible": true,
"type": "string",
"fill": false,
"width": 110
},
{
"label": "经销商 - 城市",
"prop": "dealerCity",
"visible": true,
"type": "string",
"fill": false,
"width": 110
},
{
"label": "经销商 - 代码",
"prop": "dealerCode",
"visible": true,
"type": "string",
"fill": false,
"width": 110
},
{
"label": "经销商名称",
"prop": "dealerName",
"visible": true,
"type": "string",
"fill": false,
"width": 200
},
{
"label": "经销商类型",
"prop": "dealerType",
"visible": true,
"type": "string",
"fill": false,
"width": 150
},
{
"label": "开户日期",
"prop": "openingDate",
"visible": true,
"type": "string",
"fill": false,
"width": 100
},
{
"label": "闭户日期",
"prop": "closingDate",
"visible": true,
"type": "string",
"fill": false,
"width": 100
},
{
"label": "大区总监",
"prop": "regionManager",
"visible": true,
"type": "string",
"fill": false,
"width": 100
},
{
"label": "战区经理",
"prop": "districtManager",
"visible": true,
"type": "string",
"fill": false,
"width": 100
},
{
"label": "城市经理",
"prop": "cityManager",
"visible": true,
"type": "string",
"fill": false,
"width": 100
},
{
"label": "系统名称",
"prop": "lineName",
"visible": true,
"type": "string",
"fill": false,
"width": 100
},
{
"label": "系统类型",
"prop": "lineType",
"visible": true,
"type": "string",
"fill": false,
"width": 100
},
{
"label": "渠道大类",
"prop": "channelDl",
"visible": true,
"type": "string",
"fill": false,
"width": 100
},
{
"label": "渠道小类",
"prop": "channelXl",
"visible": true,
"type": "string",
"fill": false,
"width": 100
},
{
"label": "系统业态",
"prop": "lineLf",
"visible": true,
"type": "string",
"fill": false,
"width": 100
},
{
"label": "是否直营",
"prop": "isDirect",
"visible": true,
"type": "string",
"fill": false,
"width": 100
}
],
prop: 'baseColumns',
visible: true
},
{
label: "零食折扣门店陈列计划",
children: [
{
"label": "门店编码",
"prop": "storeCode",
"visible": true,
"type": "string",
"fill": false,
"width": 150
},
{
"label": "门店名称",
"prop": "storeName",
"visible": true,
"type": "string",
"fill": false,
"width": 180
},
{
"label": "门店计数",
"prop": "storeCount",
"visible": true,
"type": "string",
"fill": false,
"width": 100
},
{
"label": "门店地址",
"prop": "storeAddress",
"visible": true,
"type": "string",
"fill": false,
"width": 280
},
{
"label": "计划 - 陈列形式",
"prop": "plannedDisplay",
"visible": true,
"type": "string",
"fill": false,
"width": 140
},
{
"label": "实际 - 陈列形式",
"prop": "actualDisplay",
"visible": true,
"type": "string",
"fill": false,
"width": 130
},
{
"label": "陈列形式是否执行",
"prop": "displayExecuted",
"visible": true,
"type": "string",
"fill": false,
"width": 150
},
{
"label": "计划 - 端架数量(组)",
"prop": "plannedEndCapQty",
"visible": true,
"type": "string",
"fill": false,
"width": 160
},
{
"label": "实际 - 端架数量(组)",
"prop": "actualEndCapQty",
"visible": true,
"type": "string",
"fill": false,
"width": 160
},
{
"label": "端架数量是否执行",
"prop": "endCapQtyExecuted",
"visible": true,
"type": "string",
"fill": false,
"width": 140
},
{
"label": "零食陈列是否执行",
"prop": "snackDisplayExecuted",
"visible": true,
"type": "string",
"fill": false,
"width": 140
},
{
"label": "计划 - 陈列费用",
"prop": "totalCost",
"visible": true,
"type": "string",
"fill": false,
"width": 120
},
{
"label": "付费陈列 - 是否",
"prop": "paidDisplay",
"visible": true,
"type": "string",
"fill": false,
"width": 120
},
{
"label": "当月拜访目标 - 是否",
"prop": "monthlyVisitTarget",
"visible": true,
"type": "string",
"fill": false,
"width": 150
}
],
prop: 'snackColumns',
visible: true
}
]);
// 表格列
const tableColumns = ref([])
// 右上角工具选择列
const chooseColumns = ref([])
// 计算列具体数据
const updateColumns = (operation) => {
return new Promise((resolve, reject) => {
if (operation === '平铺模式') {
chooseColumns.value = baseColumns.value
tableColumns.value = baseColumns.value.flatMap(item => {
if (item.children) {
return item.children.filter(child => !child.onlyFill);
}
return [];
});
} else if (operation === '填报模式') {
chooseColumns.value = baseColumns.value.flatMap(item => {
if (item.children) {
return item.children.filter(child => child.fill === true)
}
return [];
});
tableColumns.value = chooseColumns.value
}
resolve()
})
}
onMounted(() => {
updateColumns()
})
// 树形结构选择列默认有哪些
const visibleProps = computed(() => {
return baseColumns.value.flatMap(item => {
if (item.children) {
return item.children.filter(child => child.visible).map(child => child.prop);
}
return item.prop;
});
});
// 提交变更
const submitChange = async (row, col) => {
// 需要特殊处理的
// 1. 实际主货架-形式,为空时,置空实际主货架-数量
if (col.prop === 'actualMainShelfType' && !row.actualMainShelfType) {
row.actualMainShelfQty = 0
}
// 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) {
const obj = tableColumns.value.find(item => item.prop === str)
console.log('obj', obj)
if (obj.type === 'formula') {
obj.func(row)
}
}
await submitDisplayPlan({
id: row.sasdId,
[col.prop]: row[col.prop], // 当前修改列的值
...col.requestKey.reduce((acc, key) => ({ ...acc, [key]: row[key] }), {}), // 额外携带影响的列字段值
})
// // 这些提交变化的,同时会影响公式计算的,需要把公式计算的字段列结果,一起发给后台
// // 注意:实际主货架形式改变并为空时,要置空实际主货架数量
// if (col.prop === 'actualMainShelfType' && !row.actualMainShelfType) {
// row.actualMainShelfQty = ''
// // 并提交保存一次
// await submitDisplayPlan({
// id: row.sadId,
// actualMainShelfQty: row.actualMainShelfQty,
// })
// }
// // 主货架
// if (['actualMainShelfType', 'actualMainShelfQty'].includes(col.prop)) {
// await submitDisplayPlan({
// id: row.sadId,
// actualMainShelfExecuted: row.actualMainShelfExecuted,
// })
// }
// // 端架
// if (['actualEndCapQty'].includes(col.prop)) {
// await submitDisplayPlan({
// id: row.sadId,
// actualEndCapExecuted: row.actualEndCapExecuted,
// })
// }
// // 地堆
// if (['actualFloorStackArea', 'actualFloorStackQty'].includes(col.prop)) {
// await submitDisplayPlan({
// id: row.sadId,
// actualFloorStackExecuted: row.actualFloorStackExecuted,
// })
// }
// // 常规陈列是否执行
// await submitDisplayPlan({
// id: row.sadId,
// regularDisplayExecuted: row.regularDisplayExecuted,
// })
}
// 表格数据
const tableData = ref([])
const isLoading = ref(true)
const params = reactive({
pageNum: 1,
pageSize: 20,
})
const total = ref(0)
// 筛选工具
const getTableList = async () => {
isLoading.value = true
const res = await getSnackPlanList({
...params
})
res.data.rows.forEach(item => {
// 计划月份
item.salesMonth = parseTime(item.salesMonth, '{y}-{m}')
// 开户日期
item.openingDate = parseTime(item.openingDate, '{y}-{m}-{d}')
// 闭户日期
item.closingDate = parseTime(item.closingDate, '{y}-{m}-{d}')
})
tableData.value = res.data.rows
total.value = res.data.total
isLoading.value = false
}
getTableList()
</script>
<style scoped
lang="scss">
.container {
.el-tabs {
height: 100%;
display: flex;
flex-direction: column-reverse;
.el-tabs__content {
display: flex;
flex-direction: column;
height: 100%;
.el-tab-pane {
height: 100%;
display: flex;
flex-direction: column;
.pagination-container {
margin: 10px;
}
}
.formula-column {
display: flex;
justify-content: center;
align-items: center;
.el-icon {
margin-left: 2px;
}
}
}
}
.el-form-item {
align-items: center;
}
/* 表格区域 */
.auto-fit-header-table {
/* 优化超长文本的显示效果 */
.cell {
/* padding: 0 .2133rem; */
}
::v-deep(.el-table__row) {
.el-table__cell {
padding: 0;
}
}
::v-deep(.column-style) {
.cell-style {
margin: 0 -12px;
>div {
display: flex;
flex-direction: column;
align-items: flex-start;
>span {
text-align: left;
text-indent: 5px;
display: inline-block;
width: 100%;
background-color: #e1e2e6;
border-bottom: 1px solid #ebeef5;
}
}
/* 表格内下拉框 */
.el-select {
width: 100% !important;
padding: 10px;
}
.el-input {
padding: 10px;
}
}
}
}
}
</style>
\ No newline at end of file
......@@ -3,6 +3,7 @@ import pxtorem from 'postcss-pxtorem'
import { defineConfig, loadEnv } from 'vite'
import createVitePlugins from './vite/plugins'
export default defineConfig(({ mode, command }) => {
const env = loadEnv(mode, process.cwd())
const { VITE_APP_PUBLIC_PATH } = env
......
......@@ -6,9 +6,10 @@ import createSvgIcon from './svg-icon'
import createCompression from './compression'
import createSetupExtend from './setup-extend'
import createHtmlPlugin from './create-html'
import vueJsx from '@vitejs/plugin-vue-jsx' // 导入 JSX 插件
export default function createVitePlugins(viteEnv, isBuild = false) {
const vitePlugins = [vue()]
const vitePlugins = [vue(), vueJsx()]
vitePlugins.push(...createAutoImportVant())
vitePlugins.push(createAutoImport())
vitePlugins.push(createSetupExtend())
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论