提交 73a98b11 authored 作者: lidongxu's avatar lidongxu

refactor(promotion/display_schedule): 修复:店内执行上报_重构框架和架构,梳理数据层和视图层关系,增加输入数字超出计划标红提示等细节(暂时完成常规陈列部分)

上级 54734e9e
...@@ -34,14 +34,14 @@ ...@@ -34,14 +34,14 @@
"lib-flexible": "^0.3.2", "lib-flexible": "^0.3.2",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"pinia": "2.1.7", "pinia": "^3.0.4",
"splitpanes": "3.1.5", "splitpanes": "3.1.5",
"uuid": "^11.0.5", "uuid": "^11.0.5",
"vant": "^4.9.18", "vant": "^4.9.18",
"vue": "^3.5.13", "vue": "^3.5.25",
"vue-count-to": "^1.0.13", "vue-count-to": "^1.0.13",
"vue-cropper": "1.1.1", "vue-cropper": "1.1.1",
"vue-router": "4.4.0", "vue-router": "^4.6.3",
"vue3-count-to": "^1.1.2", "vue3-count-to": "^1.1.2",
"vuedraggable": "^4.1.0" "vuedraggable": "^4.1.0"
}, },
......
<template> <template>
<div class="search" v-show="showSearch"> <div class="search"
v-show="showSearch">
<el-form :inline="true" <el-form :inline="true"
:model="queryParams" :model="queryParams"
class="demo-form-inline"> class="demo-form-inline">
...@@ -28,7 +29,8 @@ ...@@ -28,7 +29,8 @@
@input="handleChange" @input="handleChange"
clearable /> clearable />
</el-form-item> </el-form-item>
<el-form-item label="门店编码/名称" v-if="showStoreSearch"> <el-form-item label="门店编码/名称"
v-if="showStoreSearch">
<el-input v-model="queryParams.storeCN" <el-input v-model="queryParams.storeCN"
placeholder="请输入门店编码/名称" placeholder="请输入门店编码/名称"
@input="handleChange" @input="handleChange"
...@@ -47,20 +49,17 @@ const props = defineProps({ ...@@ -47,20 +49,17 @@ const props = defineProps({
showStoreSearch: { showStoreSearch: {
type: Boolean, type: Boolean,
default: true default: true
},
queryParams: {
type: Object,
default: () => ({})
} }
}) })
const queryParams = reactive({
// 当月
salesMonth: new Date(),
deptName: '',
dealerCN: '',
lineNameLike: '',
storeCN: ''
})
const emits = defineEmits(['change']) const emits = defineEmits(['change'])
const handleChange = () => { const handleChange = () => {
emits('change', queryParams) emits('change')
} }
</script> </script>
......
...@@ -3,8 +3,7 @@ ...@@ -3,8 +3,7 @@
<!-- 操作类型 --> <!-- 操作类型 -->
<el-row> <el-row>
<el-form-item> <el-form-item>
<el-radio-group v-model="operation" <el-radio-group v-model="operation">
@change="checkTableColumns">
<el-radio-button label="展示模式" <el-radio-button label="展示模式"
value="展示模式" /> value="展示模式" />
<el-radio-button label="填报模式" <el-radio-button label="填报模式"
...@@ -77,13 +76,11 @@ ...@@ -77,13 +76,11 @@
</div> </div>
<!-- 公式计算 --> <!-- 公式计算 -->
<div v-else-if="col.type === 'formula'"> <div v-else-if="col.type === 'formula'">
{{ col.func(row) }} {{ row[col.prop] || '-' }}
</div> </div>
<!-- 其他类型内容 --> <!-- 其他类型内容(正常显示文字) -->
<div cllass="fill-span-wrap" <div class="fill-span-wrap"
v-else> v-else>{{ row[col.prop] || '-' }}
<!-- 正常显示 -->
<span class="fill-span">{{ row[col.prop] || '-' }}</span>
</div> </div>
</div> </div>
<!-- 展示模式 --> <!-- 展示模式 -->
...@@ -97,10 +94,10 @@ ...@@ -97,10 +94,10 @@
<pagination :total="total" <pagination :total="total"
v-model:page="params.pageNum" v-model:page="params.pageNum"
v-model:limit="params.pageSize" v-model:limit="params.pageSize"
:pageSizes="[10, 20, 50, 100]"
@pagination="getTableList" /> @pagination="getTableList" />
</div> </div>
</div> </div>
<!-- 表格弹窗 - 使用DOM移动而非复制 --> <!-- 表格弹窗 - 使用DOM移动而非复制 -->
<el-dialog v-model="showTableInDialog" <el-dialog v-model="showTableInDialog"
title="表格详情" title="表格详情"
...@@ -115,27 +112,16 @@ ...@@ -115,27 +112,16 @@
<script setup> <script setup>
import { h } from 'vue' import { h } from 'vue'
import userStore from '@/store/modules/user'
const props = defineProps({ const props = defineProps({
tableColumns: { // 表格列 tableData: { // 数据源
type: Array,
default: () => []
},
chooseColumns: { // 显隐列数据结构
type: Array,
default: () => []
},
visibleProps: { // 显隐列数据,默认勾选的列
type: Array, type: Array,
default: () => [] default: () => []
}, },
tableData: { // 数据源 baseColumns: { // 表格列
type: Array, type: Array,
default: () => [] default: () => []
}, },
isLoading: { // 表格加载状态
type: Boolean,
default: false
},
total: { // 总条数 total: { // 总条数
type: Number, type: Number,
default: 0 default: 0
...@@ -147,7 +133,7 @@ const props = defineProps({ ...@@ -147,7 +133,7 @@ const props = defineProps({
pageSize: 10, pageSize: 10,
}) })
}, },
showFill: { // 是否显示填报模式 isLoading: { // 表格加载状态
type: Boolean, type: Boolean,
default: false default: false
}, },
...@@ -156,27 +142,60 @@ const props = defineProps({ ...@@ -156,27 +142,60 @@ const props = defineProps({
default: (row, col, cellValue) => cellValue default: (row, col, cellValue) => cellValue
} }
}) })
const emit = defineEmits(['updateColumns', 'getTableList', 'updateShowSearch']) const emit = defineEmits(['getTableList', 'updateShowSearch'])
/*************** 工具栏 ***************/ /*************** 工具栏 ***************/
// 切换平铺/填报模式 const showFill = userStore().hasQcMarketEmpInfo // 是否启用填报模式
const operation = ref('展示模式'); // const operation = ref('展示模式'); // 切换平铺/填报模式
// const operation = ref('填报模式'); const operation = ref('填报模式'); // 切换平铺/填报模式
const tableRef = ref(null) const tableRef = ref(null)
const checkTableColumns = async () => { const chooseColumns = ref([]) // 右上角工具显隐列选择
// 通知外面传入 tableColumns / chooseColumns 数据源
await emit('updateColumns', operation.value) // 使用computed缓存展示列,避免重复创建
const displayModeColumns = computed(() => {
// 强制表格立即应用所有宽度设置,避免先自适应再调整 return props.baseColumns.flatMap(item => {
if (item.children) {
return item.children.filter(child => !child.onlyFill);
}
return [];
});
});
// 使用computed缓存填报列,避免重复创建
const fillModeColumns = computed(() => {
return props.baseColumns.flatMap(item => {
if (item.children) {
return item.children.filter(child => child.fill === true)
}
return [];
});
});
// 使用computed动态返回当前模式的列
const tableColumns = computed(() => {
return operation.value === '展示模式' ? displayModeColumns.value : fillModeColumns.value;
});
// 监听operation变化,更新chooseColumns并触发布局调整
watch(operation, () => {
chooseColumns.value = operation.value === '展示模式' ? props.baseColumns : fillModeColumns.value;
// 强制表格立即应用所有宽度设置
nextTick(() => { nextTick(() => {
if (tableRef.value) { if (tableRef.value) {
tableRef.value.doLayout() tableRef.value.doLayout()
} }
}) });
} }, { immediate: true });
onMounted(() => {
checkTableColumns() const visibleProps = computed(() => {
}) return props.baseColumns.flatMap(item => {
if (item.children) {
return item.children.filter(child => child.visible).map(child => child.prop);
}
return item.prop;
});
});
// 控制搜索框显隐 // 控制搜索框显隐
const showSearch = ref(true) const showSearch = ref(true)
...@@ -285,9 +304,8 @@ const handleDialogClose = () => { ...@@ -285,9 +304,8 @@ const handleDialogClose = () => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0; min-height: 0;
/* 解决flex子项内容溢出问题 */
/* overflow-y: scroll; */ /* 解决flex子项内容溢出问题 */
.auto-fit-header-table { .auto-fit-header-table {
flex: 1; flex: 1;
...@@ -326,6 +344,7 @@ const handleDialogClose = () => { ...@@ -326,6 +344,7 @@ const handleDialogClose = () => {
white-space: nowrap; white-space: nowrap;
} }
/* 自定义单元格样式(仅填充模式生效) */
.cell-style { .cell-style {
>div { >div {
display: flex; display: flex;
...@@ -383,18 +402,13 @@ const handleDialogClose = () => { ...@@ -383,18 +402,13 @@ const handleDialogClose = () => {
} }
} }
/* 填充模式下的普通战士文字 */ /* 普通文字单元格(仅填充模式生效) */
.fill-span-wrap { .fill-span-wrap {
/* line-height: 1; */
.fill-span {
display: inline-block;
width: 80% !important;
/* 超出省略号 */ /* 超出省略号 */
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
}
} }
...@@ -404,7 +418,8 @@ const handleDialogClose = () => { ...@@ -404,7 +418,8 @@ const handleDialogClose = () => {
/* 无上下内边距 */ /* 无上下内边距 */
::v-deep(.el-table__row) { ::v-deep(.el-table__row) {
.el-table__cell { .el-table__cell {
padding: 0; padding-top: 0;
padding-bottom: 0;
.cell { .cell {
padding: 0 !important; padding: 0 !important;
...@@ -419,6 +434,8 @@ const handleDialogClose = () => { ...@@ -419,6 +434,8 @@ const handleDialogClose = () => {
margin: 10px; margin: 10px;
} }
} }
</style> </style>
<style lang="scss"> <style lang="scss">
...@@ -440,6 +457,7 @@ const handleDialogClose = () => { ...@@ -440,6 +457,7 @@ const handleDialogClose = () => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden !important; overflow: hidden !important;
padding-bottom: 5px;
.dialog-table-container { .dialog-table-container {
flex: 1; flex: 1;
...@@ -457,8 +475,24 @@ const handleDialogClose = () => { ...@@ -457,8 +475,24 @@ const handleDialogClose = () => {
} }
} }
} }
}
}
// 不显示数字输入框的 spinner
.no-spinner input::-webkit-inner-spin-button,
.no-spinner input::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
.no-spinner {
-moz-appearance: textfield;
}
// 填入数字超过计划值时,红色文字提示
.red-text {
input {
color: red !important;
} }
} }
</style> </style>
\ No newline at end of file
...@@ -42,10 +42,10 @@ import ThreeTwoSeconds from './tabs/three_two_seconds.vue' ...@@ -42,10 +42,10 @@ import ThreeTwoSeconds from './tabs/three_two_seconds.vue'
import SixLittleDiamonds from './tabs/six_little_diamonds.vue' import SixLittleDiamonds from './tabs/six_little_diamonds.vue'
const activeName = ref('常规陈列'); const activeName = ref('常规陈列');
// const activeName = ref('档期计划');
const handleClickTabs = (tab) => { const handleClickTabs = (tab) => {
activeName.value = tab.name; activeName.value = tab.name;
} }
</script> </script>
<style scoped <style scoped
...@@ -71,24 +71,8 @@ const handleClickTabs = (tab) => { ...@@ -71,24 +71,8 @@ const handleClickTabs = (tab) => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.formula-column {
display: flex;
justify-content: center;
align-items: center;
.el-icon {
margin-left: 2px;
}
} }
} }
} }
.el-form-item {
align-items: center;
}
} }
}
</style> </style>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
<template> <template>
<!-- 常规陈列 --> <!-- 常规陈列 -->
<SearchList :showSearch="showSearch" <SearchList :showSearch="showSearch"
@change="searchChange" /> :queryParams="params"
@change="getTableList" />
<CustomTable :tableData="tableData" <CustomTable :tableData="tableData"
:isLoading="isLoading" :baseColumns="baseColumns"
:total="total" :total="total"
:tableColumns="tableColumns"
:chooseColumns="chooseColumns"
:visibleProps="visibleProps"
:params="params" :params="params"
:showFill="showFill" :isLoading="isLoading"
@updateColumns="updateColumns"
@getTableList="getTableList" @getTableList="getTableList"
@updateShowSearch="updateShowSearch" /> @updateShowSearch="v => showSearch.value = v" />
</template> </template>
<script setup <script setup
...@@ -21,1044 +18,29 @@ ...@@ -21,1044 +18,29 @@
import SearchList from '@/views/promotion/components/SearchList' import SearchList from '@/views/promotion/components/SearchList'
import { getDisplayList, submitDisplayPlan } from '@/api' import { getDisplayList, submitDisplayPlan } from '@/api'
import { parseTime } from '@/utils' import { parseTime } from '@/utils'
import { onMounted } from 'vue'; import { getDisplayConfig } from './data.jsx'
import userStore from '@/store/modules/user'
// 是否启用填报模式
const showFill = userStore().hasQcMarketEmpInfo
/*************** 操作类型 ***************/
// 全部列
const baseColumns = ref([
// 一级类
{
label: "基础信息", // 类型标题
children: [
// {
// label: '操作提示',
// prop: 'operationTip', // 列属性
// visible: true, // 是否显示
// type: 'string', // 列类型
// fill: true, // 是否为填报列
// fixed: 'left', // 是否固定在左侧
// width: 100,
// onlyFill: true, // 只在填报列显示
// render: (_, row, col) => {
// return (
// <div class="operation_tip_cell">
// <p>计划</p>
// <p>实际</p>
// </div>
// )
// }
// },
{
label: '计划月份', // 列标题
prop: 'salesMonth', // 列属性
visible: true, // 是否显示
type: 'string', // 列类型
fill: false, // 是否为填报列
width: 100
},
{
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: 100
},
{
label: "经销商-城市",
prop: "dealerCity",
visible: true,
type: 'string',
fill: false,
width: 100
},
{
label: "经销商-代码",
prop: "dealerCode",
visible: true,
type: 'string',
fill: false,
width: 100
},
{
label: "经销商-类型",
showOverflowTooltip: true,
prop: "dealerType",
visible: true,
type: 'string',
fill: false,
width: 100
},
{
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: "storeNameCodeDealerName", //新增动态列(只在填报列显示)
// visible: true,
// type: 'string',
// fill: true,
// // showOverflowTooltip: true,
// onlyFill: true,// 仅仅在填报模式下生效
// fixed: 'left',
// width: 225,
// render: (h, row) => {
// return h('div', {
// class: 'store-name-render-cell'
// }, [
// h('p', row.storeName),
// h('p', row.storeCode),
// h('p', row.dealerName)
// ])
// }
// },
{
label: "门店名称",
prop: "storeName",
showOverflowTooltip: true,
visible: true,
type: 'string',
fill: true,
fixed: 'left',
width: 220
},
{
label: "门店编码",
prop: "storeCode",
showOverflowTooltip: true,
visible: true,
type: 'string',
fill: true,
width: 110
},
{
label: "经销商名称",
prop: "dealerName",
showOverflowTooltip: true,
visible: true,
type: 'string',
fill: true,
// fixed: 'left',
width: 220
},
{
label: "系统类型",
prop: "lineType",
visible: true,
type: 'string',
fill: false,
fixed: 'left',
width: 100
},
{
label: "门店-省份",
prop: "storeProvince",
visible: true,
type: 'string',
fill: false,
width: 100
},
{
label: "门店-城市",
prop: "storeCity",
visible: true,
type: 'string',
fill: false,
width: 100
},
{
label: "系统名称",
prop: "lineName",
showOverflowTooltip: true,
visible: true,
type: 'string',
fill: false,
width: 100
},
{
label: "渠道大类",
prop: "channelDl",
showOverflowTooltip: true,
visible: true,
type: 'string',
fill: false,
width: 100
},
{
label: "渠道小类",
prop: "channelXl",
showOverflowTooltip: true,
visible: true,
type: 'string',
fill: false,
width: 100
},
{
label: "门店类型",
prop: "storeType",
visible: true,
type: 'string',
showOverflowTooltip: true,
fill: false,
width: 100
},
{
label: "系统业态",
prop: "systemFormat",
showOverflowTooltip: true,
visible: true,
type: 'string',
fill: false,
width: 100
},
{
label: "门店面积",
prop: "storeArea",
showOverflowTooltip: true,
visible: true,
type: 'string',
fill: false,
width: 100
},
{
label: "门店分级(销量坎级)",
prop: "storeLevel",
visible: true,
showOverflowTooltip: true,
type: 'string',
fill: false,
width: 100
},
{
label: "门店地址",
prop: "storeAddress",
showOverflowTooltip: true,
visible: true,
type: 'string',
fill: false,
width: 100
},
{
label: "品项数",
prop: "productCount",
visible: true,
type: 'string',
fill: false,
width: 100
}
],
prop: 'baseColumns',
visible: true
},
{
label: "大业态测试",
children: [
{
label: "大业态测试-动销模型",
prop: "lfSalesModel",
visible: true,
type: 'string',
fill: false,
width: 100
},
{
label: "大业态测试-月均POS",
prop: "lfMonthlyPos",
visible: true,
type: 'string',
fill: false,
width: 100
}
],
prop: 'lfColumns',
visible: true
},
{
label: "费用计划",
prop: 'fpColumns',
children: [
{
label: "主货架形式",
subLabel: '计划',
prop: "plannedMainShelfType",
visible: true,
type: 'string',
fill: true,
width: 130
},
{
label: "主货架单个费用计划",
prop: "plannedMainShelfUnitCost",
visible: true,
type: 'string',
fill: false,
width: 130
},
{
label: "主货架总费用计划",
prop: "plannedMainShelfTotalCost",
visible: true,
type: 'string',
fill: false,
width: 130
},
{
label: "主货架形式",
subLabel: '实际',
prop: "actualMainShelfType",
visible: true,
type: 'select',
options: [
{ label: '3纵', value: '3纵' },
{ 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: 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]}
class={{ 'no-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: "主货架数量",
subLabel: '计划',
prop: "plannedMainShelfQty",
visible: true,
type: 'string',
fill: true,
width: 130
},
{
label: "主货架数量",
subLabel: '实际',
prop: "actualMainShelfQty",
visible: true,
type: 'input',
// 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-input modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
{/* 限制只能输入数字 */ }
if (!/^\d*$/.test(value)) {
return;
}
const numValue = value === '' ? 0 : Number(value);
row[col.prop] = numValue;
submitChange(row, col);
}}
placeholder=""
disabled={!row[col.referenceKey]}
class={{ 'no-disabled': row[col.referenceKey] }}
clearable>
</el-input>
</div>
)
},
requestKey: ["actualMainShelfExecuted", "regularDisplayExecuted"]
},
{
label: "主货架是否",
subLabel: '按计划执行',
prop: "actualMainShelfExecuted",
visible: true,
type: 'formula',
func: (row) => {
// 如果参考值是空则返回 '-'
if (!row.plannedMainShelfType || !row.plannedMainShelfQty) {
row.actualMainShelfExecuted = '-'
return '-';
}
// 未执行情况(实际主货架形式 < 计划主货架形式)
if (parseInt(row.actualMainShelfType) < parseInt(row.plannedMainShelfType)) {
row.actualMainShelfExecuted = '未执行'
} else if (!row.actualMainShelfQty) {
row.actualMainShelfExecuted = '未执行'
} else if (parseInt(row.actualMainShelfQty) < parseInt(row.plannedMainShelfQty)) {
// 实际主货架数量 < 计划主货架数量
row.actualMainShelfExecuted = '部分执行'
} else {
// 实际主货架数量 >= 计划主货架数量
row.actualMainShelfExecuted = '执行'
}
return row.actualMainShelfExecuted;
},
formulaStr: '公式:(实际主货架形式 >= 计划主货架形式)并且(实际主货架数量 >= 计划主货架数量)',
fill: true,
width: 130
},
{
label: "端架数量",
subLabel: '计划',
prop: "plannedEndCapQty",
visible: true,
type: 'string',
fill: true,
width: 130
},
{
label: "端架总费用计划",
prop: "plannedEndCapTotalCost",
visible: true,
type: 'string',
fill: false,
width: 130
},
{
label: "端架单个费用计划",
prop: "plannedEndCapUnitCost",
visible: true,
type: 'string',
fill: false,
width: 130
},
{
label: "端架数量",
subLabel: '实际',
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: 130,
render: (_, row, col) => {
return (
<div>
{/* <span>{row[col.referenceKey] || '-'}</span> */}
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
// 判断空字符串给 0
if (!value) {
value = 0
}
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
class={{ 'no-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: ["actualEndCapExecuted", "regularDisplayExecuted"]
},
{
label: "端架是否",
subLabel: '按计划执行',
prop: "actualEndCapExecuted",
visible: true,
type: 'formula',
func: (row) => {
// 如果参考值是空则返回 '-'
if (!row.plannedEndCapQty) {
row.actualEndCapExecuted = '-'
return '-';
}
row.actualEndCapExecuted = ((parseFloat(row.actualEndCapQty) >= parseFloat(row.plannedEndCapQty))) ? '执行' : '未执行';
return row.actualEndCapExecuted;
},
formulaStr: '公式:(实际端架数量 >= 计划端架数量)',
fill: true,
width: 130
},
{
label: "地堆平米数(㎡)",
subLabel: '计划',
prop: "plannedFloorStackArea",
visible: true,
type: 'string',
fill: true,
width: 130
},
{ /*************** 表格操作相关 ***************/
label: "主题地堆是否计划",
prop: "plannedThemedFloorStack",
visible: true,
type: 'string',
fill: false,
width: 130
},
{
label: "地堆总费用计划",
prop: "plannedFloorStackTotalCost",
visible: true,
type: 'string',
fill: false,
width: 130
},
{
label: "折算1㎡单个费用计划",
prop: "plannedFloorStackUnitCostPerSqm",
visible: true,
type: 'string',
fill: false,
width: 130
},
{
label: "地堆平米数(㎡)",
subLabel: '实际',
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 },
{ label: '5', value: 5 },
{ label: '6', value: 6 },
],
referenceKey: "plannedFloorStackArea",
fill: true,
width: 130,
render: (_, row, col) => {
return (
<div>
{/* <span>{row[col.referenceKey] || '-'}</span> */}
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
// 判断空字符串给 0
if (!value) {
value = 0
}
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
class={{ 'no-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: ["actualFloorStackExecuted", "regularDisplayExecuted"]
},
{
label: "地堆数量",
subLabel: '计划',
prop: "plannedFloorStackQty",
visible: true,
type: 'string',
fill: true,
width: 130
},
{
label: "地堆数量",
subLabel: '实际',
prop: "actualFloorStackQty",
visible: true,
type: 'select',
options: [
{ label: '0', value: 0 },
{ label: '1', value: 1 },
{ label: '2', value: 2 },
{ label: '3', value: 3 },
{ label: '4', value: 4 }
],
referenceKey: "plannedFloorStackQty",
fill: true,
width: 130,
render: (_, row, col) => {
return (
<div>
{/* <span>{row[col.referenceKey] || '-'}</span> */}
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
// 判断空字符串给 0
if (!value) {
value = 0
}
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row.actualFloorStackArea || !row[col.referenceKey]}
class={{ 'no-disabled': row.actualFloorStackArea && row[col.referenceKey] }}
placeholder=""
clearable>
{col.options.map(item => (
<el-option
key={item.value}
label={item.label}
value={item.value}
/>
))}
</el-select>
</div>
)
},
// 请求时需要额外携带影响的列字段值
requestKey: ["actualFloorStackExecuted", "regularDisplayExecuted"]
},
{
label: "主题地堆是否实际",
prop: "actualThemedFloorStack",
referenceKey: "plannedThemedFloorStack",
visible: true,
type: 'select',
options: [
{ label: '是', value: '是' },
{ label: '否', value: '否' }
],
fill: false,
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>
)
}
},
{
label: "地堆是否",
subLabel: '按计划执行',
prop: "actualFloorStackExecuted",
visible: true,
type: 'formula',
func: (row) => {
// 如果参考值是空则返回 '-'
if (!row.plannedFloorStackArea || !row.plannedFloorStackQty) {
row.actualFloorStackExecuted = '-'
return '-';
}
row.actualFloorStackExecuted = ((parseFloat(row.actualFloorStackArea) >= parseFloat(row.plannedFloorStackArea)) && (parseInt(row.actualFloorStackQty) >= parseInt(row.plannedFloorStackQty))) ? '执行' : '未执行';
return row.actualFloorStackExecuted;
},
formulaStr: '公式:(实际地堆平米数 >= 计划地堆平米数)并且(实际地堆数量 >= 计划地堆数量)',
fill: true,
width: 130
},
{
label: "多点陈列数量形式",
subLabel: '计划',
prop: "plannedMultiDisplay",
visible: true,
type: 'string',
fill: true,
width: 130
},
{
label: "多点陈列总费用计划",
prop: "plannedMultiDisplayTotalCost",
visible: true,
type: 'string',
fill: false,
width: 130
},
{
label: "多点陈列数量形式",
subLabel: '实际',
prop: "actualMultiDisplay",
visible: true,
type: 'select',
options: [
{ label: '执行与计划一致', value: '执行与计划一致' },
{ label: '执行与计划不一致', value: '执行与计划不一致' }
],
referenceKey: "plannedMultiDisplay",
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]}
class={{ 'no-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: ["actualMultiDisplayExecuted"]
},
{
label: "多点陈列是否",
subLabel: '按计划执行',
prop: "actualMultiDisplayExecuted",
referenceKey: 'actualMultiDisplay',
visible: true,
type: 'formula',
func: (row) => {
// 如果参考值是空则返回 '-'
if (!row.plannedMultiDisplay) {
row.actualMultiDisplayExecuted = '-'
return '-';
}
row.actualMultiDisplayExecuted = row.actualMultiDisplay === '执行与计划一致' ? '执行' : '未执行';
return row.actualMultiDisplayExecuted;
},
formulaStr: '公式:实际与计划是否一致',
fill: true,
width: 140,
},
{
label: "挂条数量形式",
subLabel: '计划',
prop: "plannedHangingStripQuantityForm",
visible: true,
type: 'string',
fill: true,
width: 130
},
{
label: "挂条数量形式",
subLabel: '实际',
prop: "actualHangingStripQuantityForm",
visible: true,
type: 'select',
options: [
{ label: '执行与计划一致', value: '执行与计划一致' },
{ label: '执行与计划不一致', value: '执行与计划不一致' }
],
referenceKey: "plannedHangingStripQuantityForm",
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]}
class={{ 'no-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: ["actualMultiDisplayExecuted"]
},
{
label: "挂条是否",
subLabel: '按计划执行',
prop: "hangingStripExecuted",
referenceKey: 'actualHangingStripQuantityForm',
visible: true,
type: 'formula',
func: (row) => {
// 如果参考值是空则返回 '-'
if (!row.plannedHangingStripQuantityForm) {
row.hangingStripExecuted = '-'
return '-';
}
row.hangingStripExecuted = (row.actualHangingStripQuantityForm === '执行与计划一致') ? '执行' : '未执行';
return row.hangingStripExecuted;
},
formulaStr: '公式:实际与计划是否一致',
fill: true,
width: 130,
},
{
label: "合计费用-费用",
prop: "totalCost",
visible: true,
type: 'string',
fill: false,
width: 130
},
{
label: "合计费用-费率",
prop: "totalCostRate",
visible: true,
type: 'string',
fill: false,
width: 130
},
// {
// label: "常规陈列是否执行",
// prop: "regularDisplayExecuted",
// visible: true,
// type: 'formula',
// func: (row) => {
// // 实际主货架-执行 && 实际端架-执行 && 实际地堆-执行 && 实际多点陈列-执行
// // 过滤掉值为"-"的项,只对有实际值的项进行判断
// const executionStatuses = [
// row.actualMainShelfExecuted,
// row.actualEndCapExecuted,
// row.actualFloorStackExecuted,
// row.actualMultiDisplayExecuted
// ].filter(status => status !== '-');
// // 如果所有过滤后的状态都是'是',则返回'执行',否则返回'未执行'
// row.regularDisplayExecuted = executionStatuses.every(status => status === '是') ? '执行' : '未执行';
// 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: 130
},
{
label: "当月拜访目标计划",
prop: "monthlyVisitTarget",
visible: true,
type: 'string',
fill: false,
width: 130
},
{
label: "当月是否拜访实际",
prop: "monthlyVisited",
visible: true,
type: 'string',
fill: false,
width: 130
},
{
label: '修改人',
prop: 'updateBy',
visible: true,
type: 'string',
fill: false,
width: 100
},
{
label: '修改时间',
prop: 'updateTime',
visible: true,
type: 'string',
fill: false,
width: 100
}
],
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) => { const tableColumns = getDisplayConfig(async (row, col) => {
// 需要特殊处理的 // 需要特殊处理的
// 实际主货架-形式,为空时,置空实际主货架-数量 // 实际主货架-形式,为空时,置空实际主货架-数量
if (col.prop === 'actualMainShelfType' && !row.actualMainShelfType) { if (col.prop === 'actualMainShelfType' && !row.actualMainShelfType) {
row.actualMainShelfQty = 0 row.actualMainShelfQty = ''
} }
let requestObj = {} let requestObj = {}
if (col.requestKey) { if (col.requestKey) {
// 关联的公式计算列,需要特殊处理 // 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) { for (const str of col.requestKey) {
const obj = tableColumns.value.find(item => item.prop === str) // 找到目标列的列对象,调用列对象自己的公式函数进行计算
const obj = getDisplayConfig().flatMap(item => {
if (item.children) {
return item.children.filter(child => !child.onlyFill);
}
return [];
}).find(item => item.prop === str)
if (obj && obj.type === 'formula') { if (obj && obj.type === 'formula') {
obj.func(row) obj.func(row)
} }
...@@ -1069,25 +51,38 @@ ...@@ -1069,25 +51,38 @@
await submitDisplayPlan({ await submitDisplayPlan({
id: row.sadId, id: row.sadId,
[col.prop]: row[col.prop], // 当前修改列的值 [col.prop]: row[col.prop], // 当前修改列的值
...requestObj ...requestObj,
// 特殊类型数据处理(比如前端要字符串'',后端要数字0)
actualMainShelfQty: row.actualMainShelfQty || 0,
actualEndCapQty: row.actualEndCapQty || 0,
actualFloorStackArea: row.actualFloorStackArea || 0,
actualFloorStackQty: row.actualFloorStackQty || 0,
}) })
})
} // 全部列
const baseColumns = ref(tableColumns);
// 表格数据 // 表格数据
const tableData = ref([]) const tableData = ref([])
const isLoading = ref(true) const isLoading = ref(true)
let params = reactive({ const params = ref({
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
// salesMonth: new Date(),
salesMonth: '',
deptName: '',
dealerCN: '',
lineNameLike: '',
storeCN: ''
}) })
const total = ref(0) const total = ref(0)
// 筛选工具 // 筛选列表数据
const getTableList = async () => { const getTableList = async () => {
isLoading.value = true isLoading.value = true
const res = await getDisplayList({ const res = await getDisplayList({
...params ...params.value,
salesMonth: parseTime(params.value.salesMonth, '{y}-{m}')
}) })
res.data.rows.forEach(item => { res.data.rows.forEach(item => {
// 计划月份 // 计划月份
...@@ -1101,6 +96,12 @@ ...@@ -1101,6 +96,12 @@
// 动态新增列:门店名称+门店编码+经销山名称(填报模式下,合并到一起) // 动态新增列:门店名称+门店编码+经销山名称(填报模式下,合并到一起)
item.storeNameCodeDealerName = item.storeName + '\n(' + item.storeCode + ')' + '\n(' + item.dealerName + ')' item.storeNameCodeDealerName = item.storeName + '\n(' + item.storeCode + ')' + '\n(' + item.dealerName + ')'
// 特殊类型数据处理,前端要字符串'',后端要数字0
item.actualMainShelfQty = item.actualMainShelfQty || ''
item.actualEndCapQty = item.actualEndCapQty || ''
item.actualFloorStackArea = item.actualFloorStackArea || ''
item.actualFloorStackQty = item.actualFloorStackQty || ''
}) })
tableData.value = res.data.rows tableData.value = res.data.rows
total.value = res.data.total total.value = res.data.total
...@@ -1108,97 +109,10 @@ ...@@ -1108,97 +109,10 @@
} }
getTableList() getTableList()
/*************** 筛选 ***************/
const showSearch = ref(true) const showSearch = ref(true)
const updateShowSearch = (newVal) => {
showSearch.value = newVal
}
const searchChange = (newVal) => {
// 计划月份
if (newVal.salesMonth) {
newVal.salesMonth = parseTime(newVal.salesMonth, '{y}-{m}-{d}')
}
params = { ...params, ...newVal }
getTableList()
}
</script> </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;
/* 表格内下拉框 */
.el-select {
width: 100% !important;
padding: 10px;
}
.el-input {
padding: 10px;
}
}
}
}
}
</style>
<style lang="scss"> <style lang="scss">
// 动态列内容的 render 内样式 // 动态列内容的 render 内样式
// 操作提示列 // 操作提示列
...@@ -1233,4 +147,5 @@ ...@@ -1233,4 +147,5 @@
margin: 0; margin: 0;
} }
} }
</style> </style>
\ No newline at end of file
...@@ -2,20 +2,17 @@ ...@@ -2,20 +2,17 @@
<!-- 档期计划 --> <!-- 档期计划 -->
<!-- 隐藏门店搜索 --> <!-- 隐藏门店搜索 -->
<SearchList :showSearch="showSearch" <SearchList :showSearch="showSearch"
@change="searchChange" :queryParams="params"
@change="getTableList"
:showStoreSearch="false" /> :showStoreSearch="false" />
<CustomTable :tableData="tableData" <CustomTable :tableData="tableData"
:isLoading="isLoading" :baseColumns="baseColumns"
:total="total" :total="total"
:tableColumns="tableColumns"
:chooseColumns="chooseColumns"
:visibleProps="visibleProps"
:params="params" :params="params"
:showFill="showFill" :isLoading="isLoading"
:formatter="formatterFn" :formatter="formatterFn"
@updateColumns="updateColumns"
@getTableList="getTableList" @getTableList="getTableList"
@updateShowSearch="updateShowSearch" /> @updateShowSearch="v => showSearch.value = v" />
<!-- 弹窗:实际与计划不一致,让用户补充实际内容 --> <!-- 弹窗:实际与计划不一致,让用户补充实际内容 -->
<el-dialog v-model="showPromotionDialog" <el-dialog v-model="showPromotionDialog"
title="请输入实际促销机制内容" title="请输入实际促销机制内容"
...@@ -46,1281 +43,10 @@ ...@@ -46,1281 +43,10 @@
import SearchList from '@/views/promotion/components/SearchList' import SearchList from '@/views/promotion/components/SearchList'
import { getDisplayScheduleList, submitDisplaySchedulePlan } from '@/api' import { getDisplayScheduleList, submitDisplaySchedulePlan } from '@/api'
import { parseTime } from '@/utils' import { parseTime } from '@/utils'
import userStore from '@/store/modules/user' import { getSchedulePlanConfig } from './data.jsx'
import { ElMessageBox } from 'element-plus'
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
// 是否启用填报模式
const showFill = userStore().hasQcMarketEmpInfo
/*************** 操作类型 ***************/ /*************** 操作类型 ***************/
function splitAndFilter(str) {
// 分割字符串并处理空值
return str.split(/[,,]/)
.map(item => item.trim()) // 去除前后空格
.filter(item => item.length > 0); // 过滤空字符串
}
// 基础列配置
const baseColumns = ref([
{
label: "基础信息",
children: [
// {
// label: '操作提示',
// prop: 'operationTip', // 列属性
// visible: true, // 是否显示
// type: 'string', // 列类型
// fill: true, // 是否为填报列
// fixed: 'left', // 是否固定在左侧
// width: 100,
// onlyFill: true, // 只在填报列显示
// render: (_, row, col) => {
// return (
// <div class="operation_tip_cell">
// <p>计划</p>
// <p>实际</p>
// </div>
// )
// }
// },
{
label: '计划月份',
prop: 'salesMonth',
visible: true,
type: 'string',
fill: false,
width: 100
},
{
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: 100
},
{
label: "经销商-城市",
prop: "dealerCity",
visible: true,
type: "string",
fill: false,
width: 100
},
{
label: "经销商-代码",
prop: "dealerCode",
visible: true,
type: "string",
fill: false,
width: 100
},
{
label: "经销商名称",
prop: "dealerName",
visible: true,
showOverflowTooltip: true,
type: "string",
fill: true,
fixed: 'left',
width: 200
},
{
label: "经销商类型",
prop: "dealerType",
visible: true,
showOverflowTooltip: true,
type: "string",
fill: false,
width: 100
},
{
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: true,
// fixed: 'left',
width: 100
},
{
label: "系统类型",
prop: "lineType",
visible: true,
type: "string",
fill: false,
width: 100
},
{
label: "渠道大类",
showOverflowTooltip: true,
prop: "channelDl",
visible: true,
type: "string",
fill: false,
width: 100
},
{
label: "渠道小类",
prop: "channelXl",
showOverflowTooltip: true,
visible: true,
type: "string",
fill: false,
width: 100
},
{
label: "系统业态",
showOverflowTooltip: true,
prop: "lineLf",
visible: true,
type: "string",
fill: false,
width: 100
},
{
label: "门店数",
prop: "storeCount",
visible: true,
type: "string",
fill: false,
width: 100
}
],
prop: 'baseColumns',
visible: true
},
{
label: "档期基础信息",
children: [
{
label: "档期执行月份",
prop: "promotionExecutionMonth",
visible: true,
type: "string",
fill: false,
width: 100
},
{
label: "档期促销规格",
subLabel: '计划',
prop: "plannedPromotionSpec",
showOverflowTooltip: true,
visible: true,
type: "string",
fill: true,
width: 150
},
{
label: "档期促销规格",
subLabel: '实际',
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: 150,
render: (_, row, col) => {
let list = splitAndFilter(row[col.referenceKey])
list = list.map(item => ({
label: item,
value: item
})).concat([{
label: '其他',
value: '其他'
}])
return (
<div>
{/* <span>{row[col.referenceKey] || '-'}</span> */}
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={async (value) => {
row[col.prop] = value;
await submitChange(row, col);
col.checkFn(row)
}}
disabled={!row[col.referenceKey]}
class={{ 'no-disabled': row[col.referenceKey] }}
placeholder=""
clearable
>
{list.map(item => (
<el-option
key={item.value}
label={item.label}
value={item.value}
/>
))}
</el-select>
</div>
)
},
checkFn(row) {
// 检查点击选项并决定是否弹窗
if (row.actualPromotionSpec && row.actualPromotionSpec.includes('其他')) {
// 直接使用 ElMessageBox.prompt() 方法
ElMessageBox.prompt('请输入实际执行规格', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputType: 'textarea',
inputPlaceholder: '请输入实际执行规格',
// 可以设置输入框的行数
inputValidator: (value) => {
if (!value || !value.trim()) {
return '请输入实际执行规格'
}
return true
}
}).then(({ value }) => {
// 用户点击确定后,直接将输入的值赋予给 row 对象的指定属性
row.actualPromotionSpecRe = value
// 这里可以直接调用 submitChange 函数提交修改
submitChange(row, { prop: 'actualPromotionSpecRe' })
}).catch(() => {
// 用户点击取消或关闭弹窗
})
} else {
// 没选其他,则清空实际执行规格
row.actualPromotionSpecRe = ''
// 这里可以直接调用 submitChange 函数提交修改
submitChange(row, { prop: 'actualPromotionSpecRe' })
}
},
// 请求时需要额外携带影响的列字段值
requestKey: ["specExecutionStatus", "promotionExecutionStatus"]
},
{
label: "实际执行规格",
prop: "actualPromotionSpecRe",
visible: true,
type: "input",
render: (_, row, col) => {
return (
<div>
{
row.actualPromotionSpec !== '其他' ? (<div style="width: 100%; text-align: center;">-</div>) : (
<el-input modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
class='no-disabled'
placeholder="">
</el-input>
)
}
</div>
)
},
fill: true,
width: 150
},
{
label: "档期规格是否",
subLabel: '按计划执行',
prop: "specExecutionStatus",
visible: true,
type: 'formula',
func: (row) => {
// 如果参考值是空则返回 '-'
if (!row.plannedPromotionSpec) {
row.specExecutionStatus = '-';
return '-';
}
row.specExecutionStatus = row.plannedPromotionSpec === row.actualPromotionSpec ? '执行' : '未执行';
return row.specExecutionStatus;
},
formulaStr: '公式:(档期计划促销规格 = 档期执行促销规格)',
fill: true,
width: 150
},
{
label: "档期促销口味",
subLabel: '计划',
prop: "plannedPromotionFlavor",
visible: true,
type: "string",
fill: true,
width: 150
},
{
label: "档期促销口味",
subLabel: '实际',
prop: "actualPromotionFlavor",
visible: true,
type: 'select',
options: [],
referenceKey: "plannedPromotionFlavor",
fill: true,
width: 150,
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
})).concat([{
label: '其他',
value: '其他'
}])
isFullFlavor = false
}
// span 标签超出宽度时显示省略号
return (
<div>
{/* <el-tooltip placement="top" content={row[col.referenceKey] || '-'} disabled={!isContentOverflow(col.referenceKey, row[col.referenceKey], col)}>
<span >
{row[col.referenceKey] || '-'}
</span>
</el-tooltip> */}
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={async (value) => {
row[col.prop] = value;
await submitChange(row, col);
{/* 检查一下未执行/部分执行时,填写实际执行内容 */ }
col.checkFn(row)
}}
disabled={!row[col.referenceKey]}
class={{ 'no-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 >
)
},
checkFn(row) {
// 检查点击选项并决定是否弹窗
if (row.actualPromotionFlavor && row.actualPromotionFlavor.includes('其他')) {
// 直接使用 ElMessageBox.prompt() 方法
ElMessageBox.prompt('请输入实际执行口味', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputType: 'textarea',
inputPlaceholder: '请输入实际执行口味',
// 可以设置输入框的行数
inputValidator: (value) => {
if (!value || !value.trim()) {
return '请输入实际执行口味'
}
return true
}
}).then(({ value }) => {
// 用户点击确定后,直接将输入的值赋予给 row 对象的指定属性
row.actualPromotionFlavorRe = value
// 这里可以直接调用 submitChange 函数提交修改
submitChange(row, { prop: 'actualPromotionFlavorRe' })
}).catch(() => {
// 用户点击取消或关闭弹窗
})
} else {
// 没选其他,则清空实际执行口味
row.actualPromotionFlavorRe = ''
// 这里可以直接调用 submitChange 函数提交修改
submitChange(row, { prop: 'actualPromotionFlavorRe' })
}
},
// 请求时需要额外携带影响的列字段值
requestKey: ["flavorExecutionStatus", "promotionExecutionStatus"]
},
{
label: "实际执行口味",
prop: "actualPromotionFlavorRe",
visible: true,
type: "input",
render: (_, row, col) => {
return (
<div>
{
row.actualPromotionFlavor.includes('其他') ? (
<el-input modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
class='no-disabled'
placeholder="">
</el-input>
) : (<div style="width: 100%; text-align: center;">-</div>)
}
</div>
)
},
fill: true,
width: 150,
},
{
label: "档期口味是否",
subLabel: '按计划执行',
prop: "flavorExecutionStatus",
visible: true,
type: 'formula',
func: (row) => {
// 如果参考值是空则返回 '-'
if (!row.plannedPromotionFlavor) {
row.flavorExecutionStatus = '-';
return '-';
}
// 处理逗号的口味形成数组
let ppromotionFlavor = splitAndFilter(row.plannedPromotionFlavor)
if (!row.actualPromotionFlavor) {
// 判断空字符串
row.flavorExecutionStatus = '未执行';
} else if (row.actualPromotionFlavor?.length === 0) {
// 空数组
row.flavorExecutionStatus = '未执行';
} else if (row.actualPromotionFlavor?.length === 1 && row.actualPromotionFlavor[0] === '其他') {
// 数组里只有其他,则是未执行
row.flavorExecutionStatus = '未执行';
} else if (row.actualPromotionFlavor.includes('其他')) {
// 数组里包含其他,就是部分执行
row.flavorExecutionStatus = '部分执行';
} else if (row.actualPromotionFlavor?.length !== ppromotionFlavor?.length) {
// 计划的个数和实际选择的不一致,则也是部分执行
row.flavorExecutionStatus = '部分执行';
} else if (row.actualPromotionFlavor?.length === 1 && row.actualPromotionFlavor[0] === '全口味') {
// 有且只有一个,并且是全口味,则执行
row.flavorExecutionStatus = '执行';
} else if (row.actualPromotionFlavor?.length === ppromotionFlavor.length && row.actualPromotionFlavor.every(item => ppromotionFlavor.includes(item))) {
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: "档期开始时间",
subLabel: '计划',
prop: "plannedPromotionStartDate",
visible: true,
type: "string",
fill: true,
width: 150
},
{
label: "计划-补差开始时间",
prop: "plannedAdjustmentStartDate",
visible: true,
type: "string",
fill: false,
width: 140
},
{
label: "档期开始时间",
subLabel: '实际',
prop: "actualPromotionStartDate",
visible: true,
type: 'date',
referenceKey: "plannedPromotionStartDate",
fill: true,
width: 150,
render: (_, row, col) => {
return (
<div>
{/* <span>{row[col.referenceKey] || '-'}</span> */}
<div class={['date-picker', { 'no-disabled': row[col.referenceKey] }]}>
<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"
/>
</div>
</div>
)
},
requestKey: ["timeExecutionStatus", 'promotionImplementationStatus']
},
{
label: "档期结束时间",
subLabel: '计划',
prop: "plannedPromotionEndDate",
visible: true,
type: "string",
fill: true,
width: 150,
},
{
label: "计划-补差结束时间",
prop: "plannedAdjustmentEndDate",
visible: true,
type: "string",
fill: false,
width: 140
},
{
label: "档期结束时间",
subLabel: '实际',
prop: "actualPromotionEndDate",
visible: true,
type: 'date',
fill: true,
referenceKey: "plannedPromotionEndDate",
width: 150,
render: (_, row, col) => {
return (
<div>
{/* <span>{row[col.referenceKey] || '-'}</span> */}
<div class={['date-picker', { 'no-disabled': row[col.referenceKey] }]}>
<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"
/>
</div>
</div>
)
},
requestKey: ["timeExecutionStatus", 'promotionImplementationStatus']
},
{
label: "档期是否",
subLabel: '按计划执行',
prop: "promotionImplementationStatus",
visible: true,
type: 'formula',
func: (row) => {
// 如果参考值是空则返回 '-'
if (!row.plannedPromotionStartDate || !row.plannedPromotionEndDate) {
row.promotionImplementationStatus = '-';
return '-';
}
row.promotionImplementationStatus = row.actualPromotionStartDate && row.actualPromotionEndDate ? '执行' : '未执行';
return row.promotionImplementationStatus;
},
formulaStr: '公式:(实际档期开始时间不为空,并且实际档期结束时间不为空)',
fill: true,
width: 150
},
{
label: "时间是否",
subLabel: '按计划执行',
prop: "timeExecutionStatus",
visible: true,
type: 'formula',
func: (row) => {
// 如果参考值是空则返回 '-'
if (!row.plannedPromotionStartDate || !row.plannedPromotionEndDate) {
row.timeExecutionStatus = '-';
return '-';
}
row.timeExecutionStatus = (parseTime(row.actualPromotionStartDate) === parseTime(row.plannedPromotionStartDate)) && parseTime(row.actualPromotionEndDate) === parseTime(row.plannedPromotionEndDate) ? '执行' : '未执行';
return row.timeExecutionStatus;
},
formulaStr: '公式:(实际档期开始时间 = 计划档期开始时间,并且实际档期结束时间 = 计划档期结束时间)',
fill: true,
width: 150
},
{
label: "促销机制",
subLabel: '计划',
prop: "plannedPromotionMechanism",
visible: true,
type: "string",
fill: true,
width: 150
},
{
label: "促销机制",
subLabel: '实际',
prop: "actualPromotionMechanism",
visible: true,
type: 'select',
referenceKey: "plannedPromotionMechanism",
fill: true,
width: 190,
options: [
{
label: '执行与计划一致',
value: '执行与计划一致'
},
{
label: '执行与计划不一致',
value: '执行与计划不一致'
}
],
render: (_, row, col) => {
return (
<div>
{/* <span>{row[col.referenceKey] || '-'}</span> */}
<div style="width: 100%">
<el-select modelValue={row[col.prop]}
onUpdate:modelValue={async (value) => {
row[col.prop] = value;
await submitChange(row, col);
col.checkFn(row)
}}
disabled={!row[col.referenceKey]}
class={{ 'no-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>
)
},
checkFn(row) {
// 检查点击选项并决定是否弹窗
if (row.actualPromotionMechanism === '执行与计划不一致') {
// 直接使用 ElMessageBox.prompt() 方法
ElMessageBox.prompt('请输入实际执行价格', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputType: 'textarea',
inputPlaceholder: '请输入实际执行价格',
// 可以设置输入框的行数
inputValidator: (value) => {
if (!value || !value.trim()) {
return '请输入实际执行价格'
}
return true
}
}).then(({ value }) => {
// 用户点击确定后,直接将输入的值赋予给 row 对象的指定属性
row.actualPromotionPriceRe = value
// 这里可以直接调用 submitChange 函数提交修改
submitChange(row, { prop: 'actualPromotionPriceRe' })
}).catch(() => {
// 用户点击取消或关闭弹窗
})
} else {
// 没选其他,则清空实际执行价格
row.actualPromotionPriceRe = ''
// 这里可以直接调用 submitChange 函数提交修改
submitChange(row, { prop: 'actualPromotionPriceRe' })
}
}
},
{
label: "实际执行价格",
prop: "actualPromotionPriceRe",
visible: true,
type: "input",
render: (_, row, col) => {
return (
<div>
{
row.actualPromotionMechanism !== '执行与计划不一致' ? (<div style="width: 100%; text-align: center;">-</div>) : (
<el-input modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
row[col.prop] = value;
submitChange(row, col);
}}
class='no-disabled'
placeholder="">
</el-input>
)
}
</div>
)
},
fill: true,
width: 150
},
// {
// label: "促销机制是否执行",
// prop: "promotionMechanismExecutionStatus",
// visible: true,
// type: 'formula',
// func: (row) => {
// // 如果参考值是空则返回 '-'
// if (!row.plannedPromotionMechanism) {
// row.promotionMechanismExecutionStatus = '-';
// return '-';
// }
// row.promotionMechanismExecutionStatus = row.actualPromotionMechanism === '执行与计划一致' ? '执行' : '未执行';
// return row.promotionMechanismExecutionStatus;
// },
// formulaStr: '公式:实际促销机制,是否有值',
// fill: true,
// width: 150,
// },
{
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: "促销价是否",
subLabel: '按计划执行',
prop: "promotionPriceExecutionStatus",
visible: true,
type: 'formula',
func: (row) => {
row.promotionPriceExecutionStatus = row.actualPromotionMechanism === '执行与计划一致' ? '执行' : '未执行';
return row.promotionPriceExecutionStatus;
},
formulaStr: '公式:(计划促销机制 = 执行促销机制)',
fill: true,
width: 150
},
{
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) => {
// // 过滤掉值为"-"的项,只对有实际值的项进行判断
// const executionStatuses = [
// row.promotionPriceExecutionStatus,
// row.promotionMechanismExecutionStatus,
// row.specExecutionStatus,
// row.promotionImplementationStatus
// ].filter(status => status !== '-');
// // 如果所有过滤后的状态都是'是',则返回'是',否则返回'否'
// row.promotionExecutionStatus = executionStatuses.every(status => status === '是') ? '执行' : '未执行';
// return row.promotionExecutionStatus;
// },
// formulaStr: '公式:促销价执行,并且促销口味执行,并且促销规格执行,并且档期开展(某项无计划时,忽略该项)',
// fill: true,
// width: 125
// }
],
visible: true
},
{
label: "海报计划",
prop: 'posterPlan',
children: [
{
label: "海报形式",
subLabel: '计划',
prop: "plannedPosterFormat",
visible: true,
type: "string",
fill: true,
width: 150
},
{
label: "计划-海报费用",
prop: "plannedPosterCost",
visible: true,
type: "string",
fill: false,
width: 115
},
{
label: "海报形式",
subLabel: '实际',
prop: "actualPosterFormat",
visible: true,
type: 'select',
options: [
{
label: '插卡',
value: '插卡'
},
{
label: '厂商周',
value: '厂商周'
},
{
label: '电子',
value: '电子'
},
{
label: '海报',
value: '海报'
},
{
label: '跳卡',
value: '跳卡'
},
{
label: '纸质',
value: '纸质'
}
],
referenceKey: "plannedPosterFormat",
fill: true,
width: 150,
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);
}}
disabled={!row[col.referenceKey]}
class={{ 'no-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: "海报是否",
subLabel: '按计划执行',
prop: "posterExecutionStatus",
visible: true,
type: 'formula',
func: (row) => {
if (!row.plannedPosterFormat) return row.posterExecutionStatus = '-'
if (!row.actualPosterFormat) return row.posterExecutionStatus = '未执行'
row.posterExecutionStatus = row.plannedPosterFormat == row.actualPosterFormat ? '执行' : '未执行';
return row.posterExecutionStatus;
},
formulaStr: '公式:(计划海报形式 = 实际海报形式)',
fill: true,
width: 150
}
],
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: 180
}
],
visible: true
}
]);
// 表格列数据
const tableColumns = ref([])
// 右上角工具选择列
const chooseColumns = ref([])
// 计算列具体数据
const updateColumns = (operation) => {
// 保存用户选择的列配置
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)
}
return [];
});
tableColumns.value = chooseColumns.value
}
}
// 选择列默认选中哪些
const visibleProps = computed(() => {
// 分情况返回
return tableColumns.value.flatMap(item => {
if (item.children) {
return item.children.map(child => child.prop);
}
return item.prop;
});
});
// 提交变更 // 提交变更
const submitChange = async (row, col) => { const submitChange = async (row, col) => {
...@@ -1334,7 +60,7 @@ ...@@ -1334,7 +60,7 @@
if (col.requestKey) { if (col.requestKey) {
// 关联的公式计算列,需要特殊处理 // 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) { for (const str of col.requestKey) {
const obj = tableColumns.value.find(item => item.prop == str) const obj = getSchedulePlanConfig().find(item => item.prop == str)
if (obj && obj.type === 'formula') { if (obj && obj.type === 'formula') {
obj.func(row) obj.func(row)
} }
...@@ -1350,13 +76,20 @@ ...@@ -1350,13 +76,20 @@
actualPromotionStartDate: row.actualPromotionStartDate ? parseTime(row.actualPromotionStartDate, '{y}-{m}-{d}') : '', // 档期执行-促销开始日期,为字符串时,转成日期格式 actualPromotionStartDate: row.actualPromotionStartDate ? parseTime(row.actualPromotionStartDate, '{y}-{m}-{d}') : '', // 档期执行-促销开始日期,为字符串时,转成日期格式
}) })
} }
// 基础列配置
const baseColumns = ref(getSchedulePlanConfig(submitChange));
// 表格数据 // 表格数据
const tableData = ref([]) const tableData = ref([])
const isLoading = ref(true) const isLoading = ref(true)
let params = reactive({ const params = ref({
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
salesMonth: new Date(),
deptName: '',
dealerCN: '',
lineNameLike: '',
storeCN: ''
}) })
const total = ref(0) const total = ref(0)
...@@ -1364,7 +97,8 @@ ...@@ -1364,7 +97,8 @@
const getTableList = async () => { const getTableList = async () => {
isLoading.value = true isLoading.value = true
const res = await getDisplayScheduleList({ const res = await getDisplayScheduleList({
...params ...params.value,
salesMonth: parseTime(params.value.salesMonth, '{y}-{m}')
}) })
// 处理日期格式 // 处理日期格式
res.data.rows.forEach(item => { res.data.rows.forEach(item => {
...@@ -1398,44 +132,9 @@ ...@@ -1398,44 +132,9 @@
} }
onMounted(() => { onMounted(() => {
updateColumns()
getTableList() getTableList()
}) })
const isContentOverflow = (key, content, col) => {
if (!content) return false; // 没有内容则直接禁用
// 创建 DOM 计算
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().width;
// 移除临时元素
document.body.removeChild(tempSpan);
// 当内容宽度大于元素宽度时,表示内容溢出
return width > col.width;
};
const showSearch = ref(true) const showSearch = ref(true)
const updateShowSearch = (newVal) => {
showSearch.value = newVal
}
const searchChange = (newVal) => {
// 计划月份
if (newVal.salesMonth) {
newVal.salesMonth = parseTime(newVal.salesMonth, '{y}-{m}-{d}')
}
params = { ...params, ...newVal }
getTableList()
}
/*************** 弹窗 ***************/ /*************** 弹窗 ***************/
const showPromotionDialog = ref(false) const showPromotionDialog = ref(false)
...@@ -1444,11 +143,6 @@ ...@@ -1444,11 +143,6 @@
const dialogForm = reactive({ const dialogForm = reactive({
actualPromotionContent: '', actualPromotionContent: '',
}) })
// 弹窗打开
const handleDialogOpen = () => {
// 检查实际促销机制内容是否与计划促销机制内容不一致
showPromotionDialog.value = true
}
// 弹窗关闭时,清空表单数据 // 弹窗关闭时,清空表单数据
const handleDialogClose = () => { const handleDialogClose = () => {
showPromotionDialog.value = false showPromotionDialog.value = false
...@@ -1468,72 +162,3 @@ ...@@ -1468,72 +162,3 @@
} }
</script> </script>
<style scoped
lang="scss">
/* 保留原有样式,但简化了结构 */
.formula-column {
display: flex;
justify-content: center;
align-items: center;
.el-icon {
margin-left: 2px;
}
}
::v-deep(.el-table) {
.cell {
padding: 0;
}
.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;
// }
// .date-picker {
// padding: 10px;
// .el-input {
// padding: 0;
// }
// }
}
}
::v-deep(.el-date-editor) {
width: 100%;
}
</style>
\ No newline at end of file
<template> <template>
<!-- 档期陈列 --> <!-- 档期陈列 -->
<SearchList :showSearch="showSearch" <SearchList :showSearch="showSearch"
@change="searchChange" /> :queryParams="params"
@change="getTableList" />
<CustomTable :tableData="tableData" <CustomTable :tableData="tableData"
:isLoading="isLoading" :baseColumns="baseColumns"
:total="total" :total="total"
:tableColumns="tableColumns"
:chooseColumns="chooseColumns"
:visibleProps="visibleProps"
:params="params" :params="params"
:showFill="showFill" :isLoading="isLoading"
@updateColumns="updateColumns"
@getTableList="getTableList" @getTableList="getTableList"
@updateShowSearch="updateShowSearch" /> @updateShowSearch="v => showSearch.value = v" />
</template> </template>
<script setup <script setup
...@@ -21,796 +18,9 @@ ...@@ -21,796 +18,9 @@
import SearchList from '@/views/promotion/components/SearchList' import SearchList from '@/views/promotion/components/SearchList'
import { getDisplayScheduleDetail, submitDisplayScheduleDetail } from '@/api' import { getDisplayScheduleDetail, submitDisplayScheduleDetail } from '@/api'
import { parseTime } from '@/utils' import { parseTime } from '@/utils'
import { onMounted } from 'vue'; import { getScheduleDisConfig } from './data'
import userStore from '@/store/modules/user'
// 是否启用填报模式
const showFill = userStore().hasQcMarketEmpInfo
/*************** 操作类型 ***************/
// 全部列
const baseColumns = ref([
// 一级类
{
label: "基础信息", // 类型标题(用于右上角控制显隐列)
children: [
// {
// label: '操作提示', // 列标题
// prop: 'operationTip', // 列属性
// visible: true, // 是否显示
// type: 'string', // 列类型
// fill: true, // 是否在填报模式显示
// fixed: 'left', // 是否固定在左侧
// width: 100, // 列宽度
// onlyFill: true, // 只在填报列显示(展示模式不显示)
// render: (_, row, col) => { // 列自定义渲染内容
// return (
// <div class="operation_tip_cell">
// <p>计划</p>
// <p>实际</p>
// </div>
// )
// }
// },
{
label: '计划月份', // 列标题
prop: 'salesMonth', // 列属性
visible: true, // 是否显示
type: 'string', // 列类型
fill: false, // 是否为填报列
width: 100
},
{
"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": true,
"width": 200,
showOverflowTooltip: true,
fixed: 'left'
},
// {
// "label": "经销商类型",
// "prop": "dealerType",
// showOverflowTooltip: true,
// "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": true,
"width": 100,
// fixed: 'left'
},
{
"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",
// showOverflowTooltip: true,
// "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",
showOverflowTooltip: true,
"fill": false,
"width": 120
},
{
"label": "门店名称",
"prop": "storeName",
"visible": true,
"type": "string",
showOverflowTooltip: true,
"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",
// showOverflowTooltip: true,
// "visible": true,
// "type": "string",
// "fill": false,
// "width": 280
// },
{
"label": "端架数量",
"subLabel": '计划',
"prop": "plannedEndCapQty",
"visible": true,
"type": "string",
"fill": true,
"width": 140
},
// {
// "label": "计划端架 - 费用",
// "prop": "plannedEndCapTotalCost",
// "visible": true,
// "type": "string",
// "fill": false,
// "width": 140
// },
{
"label": "端架数量",
"subLabel": '实际',
"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) => {
if (!value) {
value = 0;
}
row[col.prop] = value;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
class={{'no-disabled': row[col.referenceKey]}}
placeholder=""
clearable>
{col.options.map(item => (
<el-option
key={item.value}
label={item.label}
value={item.value}
/>
))}
</el-select>
</div>
)
},
"fill": true,
"width": 140,
requestKey: ["actualEndCapExecuted", "promotionDisplayExecuted"]
},
{
"label": "端架是否",
"subLabel": '按计划执行',
"prop": "actualEndCapExecuted",
"visible": true,
"type": "formula",
"fill": true,
"width": 140,
func: (row) => {
// 如果计划端架数量为空则返回 '-'
if (!row.plannedEndCapQty) {
row.actualEndCapExecuted = '-'
return '-';
}
row.actualEndCapExecuted = row.actualEndCapQty >= row.plannedEndCapQty ? '执行' : '未执行'
return row.actualEndCapExecuted
},
formulaStr: "执行端架数量 >= 计划端架数量"
},
{
"label": "地堆平米数(㎡)",
"subLabel": '计划',
"prop": "plannedFloorStackArea",
"visible": true,
"type": "string",
"fill": true,
"width": 140
},
// {
// "label": "计划地堆 - 费用",
// "prop": "plannedFloorStackTotalCost",
// "visible": true,
// "type": "string",
// "fill": false,
// "width": 140
// },
{
"label": "地堆平米数(㎡)",
"subLabel": '实际',
"prop": "actualFloorStackArea",
"visible": true,
"type": "input",
"fill": true,
"width": 140,
referenceKey: "plannedFloorStackArea",
// "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
// },
// {
// "label": "6",
// "value": 6
// }
// ],
render: (_, row, col) => {
return (
<div>
{/* <span>{row[col.referenceKey] || '-'}</span> */}
<el-input modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
// 判断空字符串给 0
const numValue = value === '' ? 0 : Number(value);
row[col.prop] = numValue;
submitChange(row, col);
}}
min="0"
disabled={!row[col.referenceKey]}
class={{'no-disabled': row[col.referenceKey]}}
type="number"
placeholder=""
clearable>
</el-input>
</div>
)
},
requestKey: ["actualFloorStackExecuted", "promotionDisplayExecuted"]
},
{
"label": "地堆数量",
"subLabel": '计划',
"prop": "plannedFloorStackQty",
"visible": true,
"type": "string",
"fill": true,
"width": 140
},
{
"label": "地堆数量",
"subLabel": '实际',
"prop": "actualFloorStackQty",
"visible": true,
"type": "input",
"fill": true,
"width": 140,
referenceKey: "plannedFloorStackQty",
// "options": [
// {
// "label": "0",
// "value": 0
// },
// {
// "label": "1",
// "value": 1
// },
// {
// "label": "2",
// "value": 2
// },
// {
// "label": "3",
// "value": 3
// },
// {
// "label": "4",
// "value": 4
// }
// ],
render: (_, row, col) => {
return (
<div>
{/* <span>{row[col.referenceKey] || '-'}</span> */}
<el-input modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
const numValue = value === '' ? 0 : Number(value);
row[col.prop] = numValue;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
class={{'no-disabled': row[col.referenceKey]}}
min="0"
type="number"
placeholder=""
clearable>
</el-input>
</div>
)
},
requestKey: ["actualFloorStackExecuted", "promotionDisplayExecuted"]
},
{
"label": "地堆是否",
"subLabel": '按计划执行',
"prop": "actualFloorStackExecuted",
"visible": true,
"type": "formula",
"fill": true,
"width": 140,
func: (row) => {
// 如果计划端架数量为空则返回 '-'
if (!row.plannedFloorStackArea || !row.plannedFloorStackQty) {
row.actualFloorStackExecuted = '-'
return '-';
}
row.actualFloorStackExecuted = row.actualFloorStackArea >= row.plannedFloorStackArea && row.actualFloorStackQty >= row.plannedFloorStackQty ? '执行' : '未执行'
return row.actualFloorStackExecuted
},
formulaStr: "执行平米数 >= 计划平米数,并且 执行地堆数量 >= 计划地堆数量"
},
{
"label": "其他陈列数量+形式",
"subLabel": '计划',
"prop": "plannedOtherDisplay",
"visible": true,
"type": "string",
"fill": true,
"width": 140
},
// {
// "label": "计划其他陈列 - 总费用",
// "prop": "plannedOtherDisplayTotalCost",
// "visible": true,
// "type": "string",
// "fill": false,
// "width": 160
// },
{
"label": "其他陈列数量+形式",
"subLabel": '实际',
"prop": "actualOtherDisplay",
"visible": true,
"type": "select",
"options": [
{ label: '执行与计划一致', value: '执行与计划一致' },
{ label: '执行与计划不一致', value: '执行与计划不一致' }
],
"fill": true,
"width": 190,
referenceKey: "plannedOtherDisplay",
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]}
class={{'no-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: ["actualOtherDisplayExecuted", "promotionDisplayExecuted"]
},
{
"label": "其他陈列是否",
"subLabel": '按计划执行',
"prop": "actualOtherDisplayExecuted",
"visible": true,
"type": "formula",
"fill": true,
"width": 140,
func: (row) => {
if (!row.plannedOtherDisplay) {
row.actualOtherDisplayExecuted = '-'
return '-';
}
row.actualOtherDisplayExecuted = row.actualOtherDisplay === '执行与计划一致' ? '执行' : '未执行'
return row.actualOtherDisplayExecuted
},
formulaStr: "公式:实际其他陈列-数量+形式,有值"
},
{
"label": "合计费用 - 费用",
"prop": "totalCost",
"visible": true,
"type": "string",
"fill": false,
"width": 140
},
// {
// "label": "档期陈列是否执行",
// "prop": "promotionDisplayExecuted",
// "visible": true,
// "type": "formula",
// "fill": true,
// "width": 160,
// func: (row) => {
// // 实际端架-是否执行 && 实际地堆是否执行 && 实际其他陈列-是否执行
// const executionStatuses = [
// row.actualEndCapExecuted,
// row.actualFloorStackExecuted,
// row.actualOtherDisplayExecuted
// ].filter(status => status !== '-');
// // 如果所有过滤后的状态都是'是',则返回'是',否则返回'否'
// row.promotionDisplayExecuted = executionStatuses.every(status => status === '是') ? '执行' : '未执行';
// return row.promotionDisplayExecuted;
// },
// formulaStr: "公式:实际端架执行,并且实际地堆执行,并且实际其他陈列执行(某项无计划时,忽略该项)"
// },
{
"label": "付费陈列 - 是否",
"prop": "paidDisplay",
"visible": true,
"type": "string",
"fill": false,
"width": 120
},
{
"label": "当月拜访目标 - 是否",
"prop": "monthlyVisitTarget",
"visible": true,
"type": "string",
"fill": false,
"width": 160
},
{
label: '修改人',
prop: 'updateBy',
visible: true,
type: 'string',
fill: false,
width: 120
},
{
label: '修改时间',
prop: 'updateTime',
visible: true,
type: 'string',
fill: false,
width: 180
}
],
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) => { const submitChange = async (row, col) => {
// 需要特殊处理的 // 需要特殊处理的
...@@ -821,7 +31,7 @@ ...@@ -821,7 +31,7 @@
// 关联的公式计算列,需要特殊处理 // 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) { for (const str of col.requestKey) {
const obj = tableColumns.value.find(item => item.prop === str) const obj = getScheduleDisConfig().find(item => item.prop === str)
if (obj && obj.type === 'formula') { if (obj && obj.type === 'formula') {
obj.func(row) obj.func(row)
} }
...@@ -833,13 +43,20 @@ ...@@ -833,13 +43,20 @@
...col.requestKey.reduce((acc, key) => ({ ...acc, [key]: row[key] }), {}), // 额外携带影响的列字段值 ...col.requestKey.reduce((acc, key) => ({ ...acc, [key]: row[key] }), {}), // 额外携带影响的列字段值
}) })
} }
// 全部列
const baseColumns = ref(getScheduleDisConfig(submitChange));
// 表格数据 // 表格数据
const tableData = ref([]) const tableData = ref([])
const isLoading = ref(true) const isLoading = ref(true)
let params = reactive({ const params = ref({
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
salesMonth: new Date(),
deptName: '',
dealerCN: '',
lineNameLike: '',
storeCN: ''
}) })
const total = ref(0) const total = ref(0)
...@@ -847,7 +64,8 @@ ...@@ -847,7 +64,8 @@
const getTableList = async () => { const getTableList = async () => {
isLoading.value = true isLoading.value = true
const res = await getDisplayScheduleDetail({ const res = await getDisplayScheduleDetail({
...params ...params.value,
salesMonth: parseTime(params.value.salesMonth, '{y}-{m}')
}) })
res.data.rows.forEach(item => { res.data.rows.forEach(item => {
// 计划月份 // 计划月份
...@@ -878,108 +96,5 @@ ...@@ -878,108 +96,5 @@
getTableList() getTableList()
const showSearch = ref(true) const showSearch = ref(true)
const updateShowSearch = (newVal) => {
showSearch.value = newVal
}
const searchChange = (newVal) => {
// 计划月份
if (newVal.salesMonth) {
newVal.salesMonth = parseTime(newVal.salesMonth, '{y}-{m}-{d}')
}
params = { ...params, ...newVal }
getTableList()
}
</script> </script>
\ No newline at end of file
<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> <template>
<!-- 六小金刚 --> <!-- 六小金刚 -->
<SearchList :showSearch="showSearch" <SearchList :showSearch="showSearch"
@change="searchChange" /> :queryParams="params"
@change="getTableList" />
<CustomTable :tableData="tableData" <CustomTable :tableData="tableData"
:isLoading="isLoading" :baseColumns="baseColumns"
:total="total" :total="total"
:tableColumns="tableColumns"
:chooseColumns="chooseColumns"
:visibleProps="visibleProps"
:params="params" :params="params"
:showFill="showFill" :isLoading="isLoading"
@updateColumns="updateColumns" /> @getTableList="getTableList"
@updateShowSearch="v => showSearch.value = v" />
</template> </template>
<script setup <script setup
...@@ -18,341 +17,39 @@ ...@@ -18,341 +17,39 @@
import CustomTable from '@/views/promotion/components/Table' import CustomTable from '@/views/promotion/components/Table'
import SearchList from '@/views/promotion/components/SearchList' import SearchList from '@/views/promotion/components/SearchList'
import { getSixLittleDiamondsPlanList, submitSixLittleDiamondsPlan } from '@/api' import { getSixLittleDiamondsPlanList, submitSixLittleDiamondsPlan } from '@/api'
import { getSixLittleDiamondsConfig } from './data.jsx'
import { parseTime } from '@/utils'
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
/*************** 搜索列表 ***************/ /*************** 搜索列表 ***************/
const showSearch = ref(true) const showSearch = ref(true)
const searchChange = (val) => {
params.value = val
getTableList()
}
/*************** 表格 ***************/ /*************** 表格 ***************/
// 全部列 // 提交变更
const baseColumns = ref([ const submitChange = async (row, col) => {
// 基础信息分组
{
label: "基础信息",
children: [
// {
// label: '操作提示',
// prop: 'operationTip', // 列属性
// visible: true, // 是否显示
// type: 'string', // 列类型
// fill: true, // 是否为填报列
// fixed: 'left', // 是否固定在左侧
// width: 100,
// onlyFill: true, // 只在填报列显示
// render: (_, row, col) => {
// return (
// <div class="operation_tip_cell">
// <p>计划</p>
// <p>实际</p>
// </div>
// )
// }
// },
{
label: '计划月份', // 列标题
prop: 'salesMonth', // 列属性
visible: true, // 是否显示
type: 'string', // 列类型
fill: false, // 是否为填报列
width: 100
},
{
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: 120
},
{
label: '经销商-城市',
prop: 'dealerCity',
visible: true,
type: 'string',
fill: false,
width: 120
},
{
label: '经销商-代码',
prop: 'dealerCode',
visible: true,
type: 'string',
fill: false,
width: 120
},
{
label: '经销商名称',
prop: 'dealerName',
visible: true,
showOverflowTooltip: true,
type: 'string',
fill: false,
width: 200
},
{
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: 'storeCode',
visible: true,
type: 'string',
fill: false,
width: 150
},
{
label: '门店名称',
prop: 'storeName',
visible: true,
showOverflowTooltip: true,
type: 'string',
fill: true,
fixed: true,
width: 180
},
{
label: '门店-省份',
prop: 'storeProvince',
visible: true,
type: 'string',
fill: false,
width: 120
},
{
label: '门店-城市',
prop: 'storeCity',
visible: true,
type: 'string',
fill: false,
width: 120
},
{
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: 'storeType',
visible: true,
type: 'string',
fill: false,
width: 100
},
{
label: '系统业态',
prop: 'lineLf',
visible: true,
type: 'string',
fill: false,
width: 100
}
],
prop: 'baseInfoColumns',
visible: true
},
// 六小金刚批发挂网分组
{
label: "六小金刚批发挂网",
children: [
{
label: '六小金刚批发挂网',
subLabel: '计划',
prop: 'plannedGw6',
visible: true,
type: 'string',
fill: true,
width: 210
},
{
label: '六小金刚批发挂网',
subLabel: '实际',
prop: 'actualGw6',
referenceKey: 'plannedGw6',
visible: true,
type: 'select',
// options: [
// // { label: '是', value: '是' },
// // { label: '否', value: '否' }
// // { label: 0, value: 0 },
// // { label: 1, value: 1 },
// // { label: '其他', value: '其他' }
// ],
render: (_, row, col) => {
return (
<div style="padding: 10px 0;">
{/* <span>{row[col.referenceKey] || '-'}</span> */}
<el-input modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
const numValue = value === '' ? 0 : Number(value);
row[col.prop] = numValue;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
class={{'no-disabled': row[col.referenceKey]}}
min="0"
type="number"
placeholder=""
clearable>
</el-input>
</div>
)
},
fill: true,
width: 210
},
{
label: '六小金刚批发挂网陈列费',
prop: 'totalCostGw6',
visible: true,
type: 'string',
fill: false,
width: 160
},
{
label: '六小金刚批发挂网是否',
subLabel: '按实际执行',
prop: 'gwExecuted6',
visible: true,
type: 'formula',
func: (row) => {
// 如果参考值是空则返回 '-'
if (!row.plannedGw6) {
row.gwExecuted6 = '-'
return '-';
}
// 判断区间,未执行,部分执行,执行
if (!row.actualGw6 || row.actualGw6 == 0) {
row.gwExecuted6 = '未执行'
return '未执行'
} else if (row.actualGw6 < row.plannedGw6) {
row.gwExecuted6 = '部分执行'
return '部分执行'
} else {
row.gwExecuted6 = '执行'
return '执行'
}
},
formulaStr: '公式:(计划执行-六小金刚批发挂网 = 实际执行-六小金刚批发挂网)',
fill: true,
width: 210
}
],
prop: 'wholesaleNetColumns',
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 let requestObj = {}
if (col.requestKey) {
// 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) {
const obj = getSixLittleDiamondsConfig().find(item => item.prop === str)
if (obj && obj.type === 'formula') {
obj.func(row)
} }
resolve()
})
} }
// 树形结构选择列默认有哪些 requestObj = col.requestKey.reduce((acc, key) => ({ ...acc, [key]: row[key] }), {})
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
})
})
onMounted(() => { await submitSixLittleDiamondsPlan({
updateColumns() id: row.sadjId,
[col.prop]: row[col.prop], // 当前修改列的值
...requestObj
}) })
}
// 全部列
const baseColumns = ref(getSixLittleDiamondsConfig(submitChange));
const tableData = ref([]) const tableData = ref([])
const isLoading = ref(false) const isLoading = ref(false)
const total = ref(0) const total = ref(0)
...@@ -360,11 +57,18 @@ ...@@ -360,11 +57,18 @@
const params = ref({ const params = ref({
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
salesMonth: new Date(),
deptName: '',
dealerCN: '',
lineNameLike: '',
storeCN: ''
}) })
const showFill = ref(true)
const getTableList = () => { const getTableList = () => {
isLoading.value = true isLoading.value = true
getSixLittleDiamondsPlanList(params.value).then(res => { getSixLittleDiamondsPlanList({
...params.value,
salesMonth: parseTime(params.value.salesMonth, '{y}-{m}')
}).then(res => {
isLoading.value = false isLoading.value = false
tableData.value = res.data.rows.map(item => { tableData.value = res.data.rows.map(item => {
// 计划月份 // 计划月份
...@@ -375,33 +79,6 @@ ...@@ -375,33 +79,6 @@
}) })
} }
getTableList() getTableList()
const updateShowSearch = (val) => {
showSearch.value = val
}
// 提交变更
const submitChange = async (row, col) => {
let requestObj = {}
if (col.requestKey) {
// 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) {
const obj = tableColumns.value.find(item => item.prop === str)
if (obj && obj.type === 'formula') {
obj.func(row)
}
}
requestObj = col.requestKey.reduce((acc, key) => ({ ...acc, [key]: row[key] }), {})
}
await submitSixLittleDiamondsPlan({
id: row.sadjId,
[col.prop]: row[col.prop], // 当前修改列的值
...requestObj
})
}
</script> </script>
<style scoped></style> <style scoped></style>
\ No newline at end of file
<template> <template>
<!-- 零食陈列 --> <!-- 零食陈列 -->
<SearchList :showSearch="showSearch" <SearchList :showSearch="showSearch"
@change="searchChange" /> :queryParams="params"
@change="getTableList" />
<CustomTable :tableData="tableData" <CustomTable :tableData="tableData"
:isLoading="isLoading" :baseColumns="baseColumns"
:total="total" :total="total"
:tableColumns="tableColumns"
:chooseColumns="chooseColumns"
:visibleProps="visibleProps"
:params="params" :params="params"
:showFill="showFill" :isLoading="isLoading"
@updateColumns="updateColumns"
@getTableList="getTableList" @getTableList="getTableList"
@updateShowSearch="updateShowSearch" /> @updateShowSearch="v => showSearch.value = v" />
</template> </template>
<script setup <script setup
...@@ -21,543 +18,9 @@ ...@@ -21,543 +18,9 @@
import SearchList from '@/views/promotion/components/SearchList' import SearchList from '@/views/promotion/components/SearchList'
import { getSnackPlanList, submitSnackPlan } from '@/api' import { getSnackPlanList, submitSnackPlan } from '@/api'
import { parseTime } from '@/utils' import { parseTime } from '@/utils'
import { onMounted } from 'vue'; import { getSnackCofing } from './data.jsx';
import userStore from '@/store/modules/user'
// 是否启用填报模式
const showFill = userStore().hasQcMarketEmpInfo
/*************** 操作类型 ***************/ /*************** 操作类型 ***************/
// 全部列
const baseColumns = ref([
// 一级类
{
label: "基础信息",
children: [
// {
// label: '操作提示',
// prop: 'operationTip', // 列属性
// visible: true, // 是否显示
// type: 'string', // 列类型
// fill: true, // 是否为填报列
// fixed: 'left', // 是否固定在左侧
// width: 100,
// onlyFill: true, // 只在填报列显示
// render: (_, row, col) => {
// return (
// <div class="operation_tip_cell">
// <p>计划</p>
// <p>实际</p>
// </div>
// )
// }
// },
{
label: '计划月份', // 列标题
prop: 'salesMonth', // 列属性
visible: true, // 是否显示
type: 'string', // 列类型
fill: false, // 是否为填报列
width: 100
},
{
"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,
showOverflowTooltip: true,
"type": "string",
"fill": true,
"width": 200,
fixed: 'left'
},
{
"label": "经销商类型",
"prop": "dealerType",
"visible": true,
showOverflowTooltip: 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": true,
"width": 100,
fixed: 'left'
},
{
"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",
showOverflowTooltip: true,
"fill": false,
"width": 180
},
// {
// "label": "门店计数",
// "prop": "storeCount",
// "visible": true,
// "type": "string",
// "fill": false,
// "width": 100
// },
{
"label": "门店地址",
"prop": "storeAddress",
"visible": true,
showOverflowTooltip: true,
"type": "string",
"fill": false,
"width": 280
},
{
"label": "陈列形式",
subLabel: '计划',
"prop": "plannedDisplay",
"visible": true,
"type": "string",
"fill": true,
"width": 190
},
{
"label": "陈列形式",
subLabel: '实际',
"prop": "actualDisplay",
"visible": true,
"type": "input",
options: [
{
label: '执行与计划一致',
value: '执行与计划一致'
},
{
label: '执行与计划不一致',
value: '执行与计划不一致'
}
],
referenceKey: "plannedDisplay",
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]}
class={{'no-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>
)
},
"fill": true,
"width": 190,
// "options": [
// {
// "label": "端架",
// "value": "端架"
// },
// {
// "label": "收银台",
// "value": "收银台"
// },
// {
// "label": "端架和收银台",
// "value": "端架和收银台"
// }
// ],
// render: (_, row, col) => {
// return (
// <div>
// <el-tooltip placement="top" content={row[col.referenceKey] || '-'} disabled={!isContentOverflow(col.referenceKey, row[col.referenceKey], col)}>
// <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="">
// {col.options.map(item => (
// <el-option
// key={item.value}
// label={item.label}
// value={item.value}
// />
// ))}
// </el-select>
// </div>
// )
// },
// requestKey: ["displayExecuted", "snackDisplayExecuted"]
requestKey: ["displayExecuted"]
},
// {
// "label": "陈列形式是否执行",
// "prop": "displayExecuted",
// "visible": true,
// "type": "formula",
// "fill": false,
// "width": 150,
// func: (row) => {
// if (!row.plannedDisplay) {
// row.displayExecuted = '-'
// return '-';
// }
// // 截取计划陈列形式数据
// row.displayExecuted = row.actualDisplay === '执行与计划一致' ? '执行' : '未执行'
// return row.displayExecuted
// },
// formulaStr: "实际陈列形式 === 计划陈列形式"
// },
{
"label": "端架数量(组)",
subLabel: '计划',
"prop": "plannedEndCapQty",
"visible": true,
"type": "string",
"fill": true,
"width": 190
},
{
"label": "端架数量(组)",
subLabel: '实际',
"prop": "actualEndCapQty",
"visible": true,
"type": "input",
referenceKey: "plannedEndCapQty",
"fill": true,
// "options": [
// {
// "label": "0",
// "value": 0
// },
// {
// "label": "0.5",
// "value": 0.5
// },
// {
// "label": "1",
// "value": 1
// },
// {
// "label": "2",
// "value": 2
// }
// ],
render: (_, row, col) => {
return (
<div>
<div>
<el-input modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
const numValue = value === '' ? 0 : Number(value);
row[col.prop] = numValue;
submitChange(row, col);
}}
disabled={!row[col.referenceKey]}
class={{'no-disabled': row[col.referenceKey]}}
min="0"
type="number"
placeholder=""
clearable
/>
</div>
</div>
)
},
"width": 190,
// requestKey: ["endCapQtyExecuted", "snackDisplayExecuted"]
requestKey: ["endCapQtyExecuted"]
},
{
"label": "端架数量是否执行",
"prop": "endCapQtyExecuted",
"visible": true,
"type": "formula",
"fill": false,
"width": 150,
func: (row) => {
if (!row.plannedEndCapQty) {
row.endCapQtyExecuted = '-'
return '-'
}
// 截取计划端架数量数据
row.endCapQtyExecuted = row.actualEndCapQty >= row.plannedEndCapQty ? '执行' : '未执行'
return row.endCapQtyExecuted
},
formulaStr: "执行端架数量 == 计划端架数量"
},
{
"label": "零食陈列是否执行",
"prop": "snackDisplayExecuted",
"visible": true,
"type": "formula",
"fill": true,
"width": 150,
func: (row) => {
// 如果关键的 2 个计划属性无值,则直接返回 '-'
if (!row.plannedDisplay && !row.plannedEndCapQty) {
row.snackDisplayExecuted = '-'
return '-'
}
if ((row.plannedDisplay && row.actualDisplay === '执行与计划一致')) {
// 判断实际端架数量为 0 或者空时就是未执行,如果小于计划是部分执行,如果大于等于就是执行
if (!row.actualEndCapQty || row.actualEndCapQty == 0) {
row.snackDisplayExecuted = '未执行'
return '未执行'
} else if (row.actualEndCapQty < row.plannedEndCapQty) {
row.snackDisplayExecuted = '部分执行'
return '部分执行'
} else {
row.snackDisplayExecuted = '执行'
return '执行'
}
} else {
row.snackDisplayExecuted = '未执行'
return '未执行'
}
},
formulaStr: "公式:陈列形式执行,并且端架数量执行(某项无计划时,忽略该项)"
},
{
"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
},
{
label: '修改人',
prop: 'updateBy',
visible: true,
type: 'string',
fill: false,
width: 120
},
{
label: '修改时间',
prop: 'updateTime',
visible: true,
type: 'string',
fill: false,
width: 180
}
],
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) => { const submitChange = async (row, col) => {
// 需要特殊处理的 // 需要特殊处理的
...@@ -568,7 +31,7 @@ ...@@ -568,7 +31,7 @@
// 关联的公式计算列,需要特殊处理 // 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) { for (const str of col.requestKey) {
const obj = tableColumns.value.find(item => item.prop === str) const obj = getSnackCofing().find(item => item.prop === str)
if (obj && obj.type === 'formula') { if (obj && obj.type === 'formula') {
obj.func(row) obj.func(row)
} }
...@@ -617,13 +80,20 @@ ...@@ -617,13 +80,20 @@
// regularDisplayExecuted: row.regularDisplayExecuted, // regularDisplayExecuted: row.regularDisplayExecuted,
// }) // })
} }
// 全部列
const baseColumns = ref(getSnackCofing(submitChange));
// 表格数据 // 表格数据
const tableData = ref([]) const tableData = ref([])
const isLoading = ref(true) const isLoading = ref(true)
let params = reactive({ const params = ref({
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
salesMonth: new Date(),
deptName: '',
dealerCN: '',
lineNameLike: '',
storeCN: ''
}) })
const total = ref(0) const total = ref(0)
...@@ -631,7 +101,8 @@ ...@@ -631,7 +101,8 @@
const getTableList = async () => { const getTableList = async () => {
isLoading.value = true isLoading.value = true
const res = await getSnackPlanList({ const res = await getSnackPlanList({
...params ...params.value,
salesMonth: parseTime(params.value.salesMonth, '{y}-{m}')
}) })
res.data.rows.forEach(item => { res.data.rows.forEach(item => {
// 计划月份 // 计划月份
...@@ -648,42 +119,7 @@ ...@@ -648,42 +119,7 @@
isLoading.value = false isLoading.value = false
} }
getTableList() getTableList()
const isContentOverflow = (key, content, col) => {
if (!content) return false; // 没有内容则直接禁用
// 创建 DOM 计算
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().width;
// 移除临时元素
document.body.removeChild(tempSpan);
// 当内容宽度大于元素宽度时,表示内容溢出
return width > col.width;
};
const showSearch = ref(true) const showSearch = ref(true)
const updateShowSearch = (newVal) => {
showSearch.value = newVal
}
const searchChange = (newVal) => {
// 计划月份
if (newVal.salesMonth) {
newVal.salesMonth = parseTime(newVal.salesMonth, '{y}-{m}-{d}')
}
params = { ...params, ...newVal }
getTableList()
}
</script> </script>
<style scoped <style scoped
......
<template> <template>
<!-- 三米两秒 --> <!-- 三米两秒 -->
<SearchList :showSearch="showSearch" <SearchList :showSearch="showSearch"
@change="searchChange" /> :queryParams="params"
@change="getTableList" />
<CustomTable :tableData="tableData" <CustomTable :tableData="tableData"
:isLoading="isLoading" :baseColumns="baseColumns"
:total="total" :total="total"
:tableColumns="tableColumns"
:chooseColumns="chooseColumns"
:visibleProps="visibleProps"
:params="params" :params="params"
:showFill="showFill" :isLoading="isLoading"
@updateColumns="updateColumns"
@getTableList="getTableList" @getTableList="getTableList"
@updateShowSearch="updateShowSearch" /> @updateShowSearch="v => showSearch.value = v" />
</template> </template>
<script setup <script setup
...@@ -20,340 +17,41 @@ ...@@ -20,340 +17,41 @@
import CustomTable from '@/views/promotion/components/Table' import CustomTable from '@/views/promotion/components/Table'
import SearchList from '@/views/promotion/components/SearchList' import SearchList from '@/views/promotion/components/SearchList'
import { getThreeMetersTwoSecondsPlanList, submitThreeMetersTwoSecondsPlan } from '@/api' import { getThreeMetersTwoSecondsPlanList, submitThreeMetersTwoSecondsPlan } from '@/api'
import { getThreeTwoSecondsConfig } from './data.jsx'
import { parseTime } from '@/utils'
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
/*************** 搜索列表 ***************/ /*************** 搜索列表 ***************/
const showSearch = ref(true) const showSearch = ref(true)
const searchChange = (val) => {
params.value = val
getTableList()
}
/*************** 表格 ***************/ /*************** 表格 ***************/
// 全部列 // 提交变更
const baseColumns = ref([ const submitChange = async (row, col) => {
// 基础信息分组
{
label: "基础信息",
children: [
// {
// label: '操作提示',
// prop: 'operationTip', // 列属性
// visible: true, // 是否显示
// type: 'string', // 列类型
// fill: true, // 是否为填报列
// fixed: 'left', // 是否固定在左侧
// width: 100,
// onlyFill: true, // 只在填报列显示
// render: (_, row, col) => {
// return (
// <div class="operation_tip_cell">
// <p>计划</p>
// <p>实际</p>
// </div>
// )
// }
// },
{
label: '计划月份', // 列标题
prop: 'salesMonth', // 列属性
visible: true, // 是否显示
type: 'string', // 列类型
fill: false, // 是否为填报列
width: 100
},
{
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: 120
},
{
label: '经销商-城市',
prop: 'dealerCity',
visible: true,
type: 'string',
fill: false,
width: 120
},
{
label: '经销商-代码',
prop: 'dealerCode',
visible: true,
type: 'string',
fill: false,
width: 120
},
{
label: '经销商名称',
prop: 'dealerName',
visible: true,
showOverflowTooltip: true,
type: 'string',
fill: false,
width: 200
},
{
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: 'storeCode',
visible: true,
type: 'string',
fill: false,
width: 150
},
{
label: '门店名称',
prop: 'storeName',
visible: true,
showOverflowTooltip: true,
type: 'string',
fill: true,
fixed: true,
width: 180
},
{
label: '门店-省份',
prop: 'storeProvince',
visible: true,
type: 'string',
fill: false,
width: 120
},
{
label: '门店-城市',
prop: 'storeCity',
visible: true,
type: 'string',
fill: false,
width: 120
},
{
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: 'storeType',
visible: true,
type: 'string',
fill: false,
width: 100
},
{
label: '系统业态',
prop: 'lineLf',
visible: true,
type: 'string',
fill: false,
width: 100
}
],
prop: 'baseInfoColumns',
visible: true
},
// 60g挂条陈列分组
{
label: "60g挂条陈列",
children: [
{
label: '60g挂条陈列数量',
subLabel: '计划',
prop: 'plannedDisplayQty60',
visible: true,
type: 'string',
fill: true,
width: 200
},
{
label: '60g挂条陈列费',
prop: 'totalCost60',
visible: true,
type: 'string',
fill: false,
width: 140
},
{
label: '60g挂条陈列数量',
subLabel: '实际',
prop: 'actualDisplayQty60',
referenceKey: 'plannedDisplayQty60',
visible: true,
type: 'input',
fill: true,
width: 200,
render: (_, row, col) => {
return (
<div style="padding: 10px 0;">
{/* <span>{row[col.referenceKey] || '-'}</span> */}
<div>
<el-input
modelValue={row[col.prop]}
onUpdate:modelValue={(value) => {
const numValue = value === '' ? 0 : Number(value);
row[col.prop] = numValue;
submitChange(row, col);
}}
min="0"
type="number"
disabled={!row[col.referenceKey]}
class={{'no-disabled': row[col.referenceKey]}}
placeholder=""
clearable
/>
</div>
</div>
);
},
requestKey: ["displayQtyExecuted60"]
},
// 60g挂条陈列-是否执行
{
label: '60g挂条陈列是否',
subLabel: '按计划执行',
prop: 'displayQtyExecuted60',
visible: true,
type: 'formula',
func: (row) => {
// 如果参考值是空则返回 '-'
if (!row.plannedDisplayQty60) {
row.displayQtyExecuted60 = '-';
return '-';
}
// 判断区间,未执行,部分执行,执行
if (!row.actualDisplayQty60 || row.actualDisplayQty60 == 0) {
row.displayQtyExecuted60 = '未执行'
return '未执行'
} else if (row.actualDisplayQty60 * 1 < row.plannedDisplayQty60 * 1) {
row.displayQtyExecuted60 = '部分执行'
return '部分执行'
} else {
row.displayQtyExecuted60 = '执行'
return '执行'
}
},
formulaStr: '公式:(实际-60g挂条陈列数量 >= 计划-60g挂条陈列数量)',
fill: true,
width: 200
},
],
prop: 'sixtyGramHangBarColumns',
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 let requestObj = {}
if (col.requestKey) {
// 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) {
const obj = getThreeTwoSecondsConfig().find(item => item.prop === str)
if (obj && obj.type === 'formula') {
obj.func(row)
} }
resolve()
})
} }
// 树形结构选择列默认有哪些 requestObj = col.requestKey.reduce((acc, key) => ({ ...acc, [key]: row[key] }), {})
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
})
})
onMounted(() => { await submitThreeMetersTwoSecondsPlan({
updateColumns() id: row.sadsId,
[col.prop]: row[col.prop], // 当前修改列的值
...requestObj
}) })
}
// 全部列
const baseColumns = ref(getThreeTwoSecondsConfig(submitChange));
const tableData = ref([]) const tableData = ref([])
const isLoading = ref(false) const isLoading = ref(false)
const total = ref(0) const total = ref(0)
...@@ -361,11 +59,18 @@ ...@@ -361,11 +59,18 @@
const params = ref({ const params = ref({
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
salesMonth: new Date(),
deptName: '',
dealerCN: '',
lineNameLike: '',
storeCN: ''
}) })
const showFill = ref(true)
const getTableList = () => { const getTableList = () => {
isLoading.value = true isLoading.value = true
getThreeMetersTwoSecondsPlanList(params.value).then(res => { getThreeMetersTwoSecondsPlanList({
...params.value,
salesMonth: proxy.parseTime(params.value.salesMonth, '{y}-{m}')
}).then(res => {
isLoading.value = false isLoading.value = false
tableData.value = res.data.rows.map(item => { tableData.value = res.data.rows.map(item => {
// 计划月份 // 计划月份
...@@ -377,35 +82,6 @@ ...@@ -377,35 +82,6 @@
} }
getTableList() getTableList()
const updateShowSearch = (val) => {
showSearch.value = val
}
// 提交变更
const submitChange = async (row, col) => {
let requestObj = {}
if (col.requestKey) {
// 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) {
const obj = tableColumns.value.find(item => item.prop === str)
if (obj && obj.type === 'formula') {
obj.func(row)
}
}
requestObj = col.requestKey.reduce((acc, key) => ({ ...acc, [key]: row[key] }), {})
}
await submitThreeMetersTwoSecondsPlan({
id: row.sadsId,
[col.prop]: row[col.prop], // 当前修改列的值
...requestObj
})
}
</script> </script>
<style scoped></style> <style scoped></style>
\ No newline at end of file
...@@ -430,9 +430,8 @@ const lookPhoto = async (row) => { // 查看照片 ...@@ -430,9 +430,8 @@ const lookPhoto = async (row) => { // 查看照片
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 1; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
::v-deep(.el-overlay) { ::v-deep(.el-overlay) {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论