提交 017cd04d authored 作者: lidongxu's avatar lidongxu

refactor(mobile/pages/storeexecution/report): 新增:勤策App移动端_新增店内执行上报预览功能

上级 8fb7776d
...@@ -89,7 +89,8 @@ const handleIconClick = (icon) => { ...@@ -89,7 +89,8 @@ const handleIconClick = (icon) => {
.mobile-container { .mobile-container {
background-color: #f3f3f3; background-color: #f3f3f3;
min-height: 100vh; min-height: 100vh;
font-size: 14px; font-size: 13px;
// font-size: 12px;
padding: 20px; padding: 20px;
p { p {
...@@ -122,7 +123,7 @@ const handleIconClick = (icon) => { ...@@ -122,7 +123,7 @@ const handleIconClick = (icon) => {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
/* 创建4列等宽布局 */ /* 创建4列等宽布局 */
gap: 10px; gap: 2px;
/* 列之间的间距 */ /* 列之间的间距 */
justify-content: space-between; justify-content: space-between;
/* 两边对齐 */ /* 两边对齐 */
......
<template>
<!-- 抽屉搜索组件 -->
<van-popup v-model:show="showDrawer"
position="right"
class="search-drawer"
:style="{ width: '85%', height: '100%' }"
teleport="body"
@close="handleClose">
<!-- 抽屉头部 -->
<van-nav-bar title="筛选条件"
left-text="关闭"
left-arrow
@click-left="handleClose" />
<!-- 搜索表单内容 -->
<div class="search-content">
<van-cell-group inset>
<!-- 计划月份 -->
<van-field :modelValue="parseTime(queryParams.salesMonth, '{y}-{m}')"
label="计划月份"
placeholder="选择计划月份"
readonly
is-link
@click="showMonthPicker = true">
<template #input>
<span>{{ queryParams.salesMonth || '请选择' }}</span>
</template>
</van-field>
<!-- 大区/战区 -->
<van-field v-model="queryParams.deptName"
label="大区/战区"
placeholder="请输入大区/战区"
@update:model-value="handleChange"
clearable />
<!-- 经销商编码/名称 -->
<van-field v-model="queryParams.dealerCN"
label="经销商编码/名称"
placeholder="请输入经销商编码/名称"
@update:model-value="handleChange"
clearable />
<!-- 系统名称 -->
<van-field v-model="queryParams.lineNameLike"
label="系统名称"
placeholder="请输入系统名称"
@update:model-value="handleChange"
clearable />
<!-- 门店编码/名称 -->
<van-field v-if="showStoreSearch"
v-model="queryParams.storeCN"
label="门店编码/名称"
placeholder="请输入门店编码/名称"
@update:model-value="handleChange"
clearable />
<!-- 数据筛选 -->
<van-field v-model="queryParams.rqStatus"
label="数据筛选"
placeholder="请选择筛选状态"
readonly
is-link
@click="showStatusPicker = true">
<template #input>
<span>{{ getStatusText(queryParams.rqStatus) }}</span>
</template>
</van-field>
</van-cell-group>
<!-- 月份选择器 -->
<van-popup v-model:show="showMonthPicker"
position="bottom"
teleport="body">
<van-date-picker v-model="currentMonth"
title="选择计划月份"
:columns-type="['year', 'month']"
:min-date="minDate"
:max-date="maxDate"
@confirm="confirmMonth"
@cancel="showMonthPicker = false" />
</van-popup>
<!-- 状态选择器 -->
<van-popup v-model:show="showStatusPicker"
position="bottom"
teleport="body">
<van-picker :columns="statusOptions"
@confirm="confirmStatus"
@cancel="showStatusPicker = false" />
</van-popup>
<!-- 操作按钮 -->
<div class="action-buttons">
<van-button type="default"
size="large"
@click="handleReset"
class="reset-btn">
<van-icon name="replay" /> 重置
</van-button>
<van-button type="primary"
size="large"
@click="handleConfirm"
class="confirm-btn">
<van-icon name="success" /> 确定
</van-button>
</div>
</div>
</van-popup>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { parseTime } from '@/utils'
const { proxy } = getCurrentInstance()
const props = defineProps({
showSearch: {
type: Boolean,
default: false
},
showStoreSearch: {
type: Boolean,
default: true
},
queryParams: {
type: Object,
default: () => ({})
}
})
const emits = defineEmits(['change', 'update:showSearch'])
// 抽屉显示状态
const showDrawer = computed({
get: () => props.showSearch,
set: (value) => emits('update:showSearch', value)
})
// 选择器状态
const showMonthPicker = ref(false)
const showStatusPicker = ref(false)
const currentMonth = ref([])
// 日期范围
const minDate = new Date(2016, 0, 1)
const maxDate = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 31)
// 状态选项
const statusOptions = [
{ text: '全部', value: '' },
{ text: '未执行', value: '未执行' }
]
// 获取状态文本
const getStatusText = (value) => {
const option = statusOptions.find(item => item.value === value)
return option ? option.text : '全部'
}
// 确认月份选择
const confirmMonth = ({ selectedValues: value }) => {
// value 是 [2025, 2] 这种格式
const year = value[0]
const month = String(value[1]).padStart(2, '0')
props.queryParams.salesMonth = `${year}-${month}`
showMonthPicker.value = false
handleChange()
}
// 确认状态选择
const confirmStatus = (value) => {
props.queryParams.rqStatus = value.selectedOptions[0]?.value || ''
showStatusPicker.value = false
handleChange()
}
// 处理变化
const handleChange = () => {
emits('change')
}
// 关闭抽屉
const handleClose = () => {
showDrawer.value = false
}
// 重置表单
const handleReset = () => {
// 重置所有查询参数
Object.keys(props.queryParams).forEach(key => {
if (key !== 'pageNum' && key !== 'pageSize') {
props.queryParams[key] = ''
}
})
handleChange()
}
// 确定搜索
const handleConfirm = () => {
handleChange()
handleClose()
}
// 监听外部showSearch变化
watch(() => props.showSearch, (newVal) => {
if (newVal) {
// 打开抽屉时初始化当前月份
if (props.queryParams.salesMonth) {
// 如果是日期类型转成字符串
if (props.queryParams.salesMonth instanceof Date) {
props.queryParams.salesMonth = props.queryParams.salesMonth.toISOString().split('T')[0].substring(0, 7)
}
const [year, month] = props.queryParams.salesMonth.split('-')
// currentMonth = [2025, 02] 这种格式
currentMonth.value = [parseInt(year), parseInt(month)]
} else {
// 当前年月日
currentMonth.value = [new Date().getFullYear(), new Date().getMonth()]
}
}
})
</script>
<style scoped
lang="scss">
.search-drawer {
.search-content {
height: calc(100% - 46px); // 减去导航栏高度
overflow-y: auto;
padding: 16px;
.van-cell-group {
margin-bottom: 20px;
}
.action-buttons {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
padding: 16px;
display: flex;
gap: 12px;
border-top: 1px solid #ebedf0;
.reset-btn,
.confirm-btn {
flex: 1;
:deep(.van-button__content) {
display: flex;
align-items: center;
gap: 4px;
}
}
}
}
}
</style>
\ No newline at end of file
<template>
<div class="flex-container">
<!-- 操作类型 -->
<!-- 表格容器 - 用于DOM移动 -->
<div ref="tableContainer"
class="table-container">
<!-- 数据表格 -->
<el-table :data="tableData"
border
ref="tableRef"
class="auto-fit-header-table"
v-loading="isLoading"
show-overflow-tooltip
:class="{ 'cell-no-padding': operation === '填报模式' }"
:row-style="tableRowStyle">
<template v-for="col in tableColumns">
<el-table-column v-if="col.visible"
:label="col.label"
:prop="col.prop"
:key="col.prop"
align="center"
class-name="column-style"
:width="col.width"
:fixed="operation === '填报模式' && col.fixed">
<template #header="{ column }">
<!-- 只为特定公式列添加问号图标 -->
<span class="formula-column">
<div class="column">
<p>{{ column.label }}</p>
<p v-if="col.subLabel">{{ col.subLabel }}</p>
</div>
<el-tooltip v-if="col.type === 'formula'"
:content="col.formulaStr"
placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</span>
</template>
<template #default="{ row }">
<!-- 填报模式 -->
<div v-if="operation === '填报模式'">
<!-- 自定义渲染内容单元格 -->
<div v-if="col.render"
class="cell-style">
<component :is="col.render(h, row, col, !isCurrentMonth(row.salesMonth))" />
</div>
<!-- 公式计算 -->
<div v-else-if="col.type === 'formula'">
{{ row[col.prop] || '-' }}
</div>
<!-- 其他类型内容(正常显示文字) -->
<div class="fill-span-wrap"
v-else>{{ row[col.prop] || '-' }}
</div>
</div>
<!-- 展示模式 -->
<div class="show-cell-style"
v-else>{{ formatter(row, col, row[col.prop] || '-') }}</div>
</template>
</el-table-column>
</template>
</el-table>
<!-- 分页 -->
<pagination :total="total"
:layout="'prev,pager,next'"
v-model:page="params.pageNum"
v-model:limit="params.pageSize"
:pageSizes="[10, 20, 50, 100]"
@pagination="getTableList" />
</div>
</div>
<!-- 表格弹窗 - 使用DOM移动而非复制 -->
<el-dialog v-model="showTableInDialog"
title="表格详情"
modal-class="table-dialog-modal"
fullscreen
:before-close="handleDialogClose">
<!-- 弹窗内的表格容器 -->
<div ref="dialogTableContainer"
class="dialog-table-container"></div>
</el-dialog>
</template>
<script setup>
import { h } from 'vue'
import userStore from '@/store/modules/user'
const props = defineProps({
tableData: { // 数据源
type: Array,
default: () => []
},
baseColumns: { // 表格列
type: Array,
default: () => []
},
total: { // 总条数
type: Number,
default: 0
},
params: { // 分页参数
type: Object,
default: () => ({
pageNum: 1,
pageSize: 10,
})
},
isLoading: { // 表格加载状态
type: Boolean,
default: false
},
formatter: { // 格式化函数
type: Function,
default: (row, col, cellValue) => cellValue
}
})
const emit = defineEmits(['getTableList', 'updateShowSearch'])
/*************** 工具栏 ***************/
const showFill = userStore().hasQcMarketEmpInfo // 是否启用填报模式
const operation = ref('展示模式'); // 切换平铺/填报模式
// const operation = ref('填报模式'); // 切换平铺/填报模式
const tableRef = ref(null)
const chooseColumns = ref([]) // 右上角工具显隐列选择
// 使用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(() => {
if (tableRef.value) {
tableRef.value.doLayout()
}
});
}, { immediate: true });
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)
watch(showSearch, (newVal) => {
emit('updateShowSearch', newVal)
})
// 刷新数据
const getTableList = () => {
emit('getTableList')
}
/************** 弹框控制 ***************/
// 控制表格弹框显隐
const tableContainer = ref(null) // 原始表格容器引用
const dialogTableContainer = ref(null) // 弹窗内表格容器引用
const showTableInDialog = ref(false)
// DOM移动需要的引用
let containerElement = null // 保存整个可移动容器
let originalParent = null // 保存原始父容器
// 打开表格弹窗并移动DOM
const openTableDialog = async () => {
// 保存容器元素和原始父容器引用
if (tableContainer.value) {
containerElement = tableContainer.value
originalParent = containerElement.parentElement
}
// 显示弹窗
showTableInDialog.value = true
// 等待弹窗渲染完成后移动DOM
await nextTick()
moveContentToDialog()
}
// 移动内容到弹窗
const moveContentToDialog = () => {
if (containerElement && dialogTableContainer.value) {
// 关键DOM移动操作 - 现在会移动整个容器(包含表格和分页器)
dialogTableContainer.value.appendChild(containerElement)
// 调整表格布局
nextTick(() => {
if (tableRef.value) {
tableRef.value.doLayout()
}
})
}
}
// 移动内容回原位
const moveContentBack = () => {
if (containerElement && originalParent) {
// 关键DOM移动操作
originalParent.appendChild(containerElement)
// 调整表格布局
nextTick(() => {
if (tableRef.value) {
tableRef.value.doLayout()
}
})
}
}
// 关闭弹窗
const handleDialogClose = () => {
// 先移动内容回原位
moveContentBack()
// 然后关闭弹窗
showTableInDialog.value = false
}
/*************** 非当月置灰行 ***************/
// 获取当前月份的函数
const getCurrentMonth = () => {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
return `${year}-${month}`;
};
// 检查是否为当前月的函数
const isCurrentMonth = (salesMonth) => {
return salesMonth === getCurrentMonth();
};
// 行样式函数
const tableRowStyle = ({ row }) => {
if (!isCurrentMonth(row.salesMonth)) {
return { backgroundColor: '#f5f7fa' }; // 灰色背景
}
return {};
};
</script>
<style scoped
lang="scss">
/* 根容器设置为flex布局,占据整个可用空间 */
.flex-container {
flex: 1;
display: flex;
flex-direction: column;
/* 工具栏 */
.el-row {
.el-form-item {
.tip-title {
font-size: 32px;
color: red;
margin-left: 50px;
font-weight: 900;
margin-top: -5px;
}
}
}
}
/* 因为表格会被挪走,所以必须独立样式无法嵌套在别人内 */
/* 表格样式 */
.table-container {
flex: 1;
/* 这是关键,让表格容器自动占据剩余空间 */
display: flex;
flex-direction: column;
// min-height: 0;
// overflow-y: scroll;
/* 解决flex子项内容溢出问题 */
.auto-fit-header-table {
flex: 1;
/* 列样式 */
.column-style {
/* 列头样式 */
.formula-column {
display: flex;
justify-content: center;
align-items: center;
.column {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
p {
margin: 0;
font-size: 12px;
color: #606266;
}
}
.el-icon {
margin-left: 2px;
}
}
/* 单元格样式 */
/* 展示模式单元格(仅展示模式生效) */
.show-cell-style {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 自定义单元格样式(仅填充模式生效) */
.cell-style {
>div {
display: flex;
flex-direction: column;
align-items: center;
>span {
text-align: left;
text-indent: 5px;
display: inline-block;
width: 80%;
background-color: #e1e2e6;
border-bottom: 1px solid #ebeef5;
}
}
/* 表格内下拉框 */
.el-select {
width: 100% !important;
padding: 10px;
/* 非 disabled 状态下的背景颜色 */
&.no-disabled ::v-deep(.el-select__wrapper) {
border: 4px solid var(--el-background-editor-cell) !important;
}
}
.el-input {
height: 32px !important;
/* box-sizing: content-box; */
padding: 0 10px;
width: 100%;
/* 非 disabled 状态下的背景颜色 */
&.no-disabled ::v-deep(.el-input__wrapper) {
border: 4px solid var(--el-background-editor-cell) !important;
}
}
.date-picker {
width: 100%;
padding: 10px;
/* 非 disabled 状态下的背景颜色 */
&.no-disabled ::v-deep(.el-input__wrapper) {
border: 4px solid var(--el-background-editor-cell) !important;
}
::v-deep(.el-input) {
width: 100%;
padding: 0;
}
}
}
/* 普通文字单元格(仅填充模式生效) */
.fill-span-wrap {
/* 超出省略号 */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 10px;
}
}
/* 填报模式-单元格 */
&.cell-no-padding {
/* 无上下内边距 */
::v-deep(.el-table__row) {
.el-table__cell {
padding-top: 0;
padding-bottom: 0;
.cell {
padding: 0 !important;
}
}
}
}
}
/* 分页器 */
.pagination-container {
margin: 10px;
}
}
</style>
<template>
<div class="app-container">
<div class="container">
<van-nav-bar fixed
left-arrow
@click-left="router.back()"
title="店内执行上报浏览"
right-text="筛选"
@click-right="handleClickRight" />
<SearchList v-model:showSearch="showSearch"
:queryParams="params"
@change="getTableList" />
<el-tabs v-model="activeName"
class="demo-tabs"
@tab-click="handleClickTabs">
<el-tab-pane label="常规陈列"
name="常规陈列">
<Display :params="params" ref="display"/>
</el-tab-pane>
<el-tab-pane label="档期计划"
name="档期计划">
<Schedule :params="params" ref="schedule"/>
</el-tab-pane>
<el-tab-pane label="档期陈列"
name="档期陈列">
<ScheduleDis :params="params" ref="scheduleDis"/>
</el-tab-pane>
<el-tab-pane label="零食陈列"
name="零食陈列">
<Snack :params="params" ref="snack"/>
</el-tab-pane>
<el-tab-pane label="三米两秒"
name="三米两秒">
<ThreeTwoSeconds :params="params" ref="threeTwoSeconds"/>
</el-tab-pane>
<el-tab-pane label="六小金刚"
name="六小金刚">
<SixLittleDiamonds :params="params" ref="sixLittleDiamonds"/>
</el-tab-pane>
</el-tabs>
</div>
</div>
</template>
<script setup>
import Display from './tabs/display.vue'
import Schedule from './tabs/schedule.vue'
import ScheduleDis from './tabs/schedule_dis.vue'
import Snack from './tabs/snack.vue'
import ThreeTwoSeconds from './tabs/three_two_seconds.vue'
import SixLittleDiamonds from './tabs/six_little_diamonds.vue'
import SearchList from './components/SearchList'
const router = useRouter()
const activeName = ref('常规陈列');
const handleClickTabs = (tab) => {
activeName.value = tab.name;
}
const showSearch = ref(false)
const handleClickRight = () => {
showSearch.value = true;
}
const params = ref({
pageNum: 1,
pageSize: 50,
salesMonth: new Date(),
deptName: '',
dealerCN: '',
lineNameLike: '',
storeCN: ''
})
const display = ref(null)
const schedule = ref(null)
const scheduleDis = ref(null)
const snack = ref(null)
const threeTwoSeconds = ref(null)
const sixLittleDiamonds = ref(null)
const getTableList = () => {
display.value.getTableList()
schedule.value.getTableList()
scheduleDis.value.getTableList()
snack.value.getTableList()
threeTwoSeconds.value.getTableList()
sixLittleDiamonds.value.getTableList()
}
</script>
<style scoped
lang="scss">
.app-container {
padding: 10px;
padding-top: 46px;
.container {
padding: 0px;
.el-tabs {
height: 100%;
display: flex;
flex-direction: column-reverse;
::v-deep(.el-tabs__header) {
// 确保底部边框线完整显示
border-bottom: 1px solid var(--el-border-color-light);
width: 100%;
}
::v-deep(.el-tabs__nav-wrap) {
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch; // iOS平滑滚动
scrollbar-width: none; // Firefox隐藏滚动条
&::after {
// 隐藏el-tabs默认的底部伪元素边框
display: none;
}
&::-webkit-scrollbar {
display: none; // Chrome/Safari隐藏滚动条
}
}
::v-deep(.el-tabs__nav-scroll) {
overflow: visible;
width: max-content;
}
::v-deep(.el-tabs__content) {
display: flex;
flex-direction: column;
overflow-y: scroll;
// height: 100%;
.el-tab-pane {
// height: 100%;
display: flex;
flex-direction: column;
}
}
}
}
}
</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>
<!-- 常规陈列 -->
<CustomTable :tableData="tableData"
:baseColumns="baseColumns"
:total="total"
:params="params"
:isLoading="isLoading"
@getTableList="getTableList"
@updateShowSearch="v => showSearch.value = v" />
</template>
<script setup
lang="jsx">
import CustomTable from '../components/Table'
import SearchList from '../components/SearchList'
import { getDisplayList, submitDisplayPlan } from '@/api'
import { parseTime } from '@/utils'
import { getDisplayConfig } from './data.jsx'
/*************** 表格操作相关 ***************/
const props = defineProps({
params: {
type: Object,
default: () => ({})
}
})
// 提交变更
const tableColumns = getDisplayConfig(async (row, col) => {
// 需要特殊处理的
// 实际主货架-形式,为空时,置空实际主货架-数量
if (col.prop === 'actualMainShelfType' && !row.actualMainShelfType) {
row.actualMainShelfQty = ''
}
let requestObj = {}
if (col.requestKey) {
// 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) {
// 找到目标列的列对象,调用列对象自己的公式函数进行计算
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') {
obj.func(row)
}
}
requestObj = col.requestKey.reduce((acc, key) => ({ ...acc, [key]: row[key] }), {})
}
await submitDisplayPlan({
id: row.sadId,
[col.prop]: row[col.prop], // 当前修改列的值
...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 isLoading = ref(true)
// const params = ref({
// pageNum: 1,
// pageSize: 50,
// salesMonth: new Date(),
// deptName: '',
// dealerCN: '',
// lineNameLike: '',
// storeCN: ''
// })
const total = ref(0)
// 筛选列表数据
const getTableList = async () => {
isLoading.value = true
const res = await getDisplayList({
...props.params,
salesMonth: parseTime(props.params.salesMonth, '{y}-{m}')
})
res.data.rows.forEach(item => {
// 计划月份
item.salesMonth = parseTime(item.salesMonth, '{y}-{m}')
// 开户日期
item.openingDate = parseTime(item.openingDate, '{y}-{m}-{d}')
// 闭户日期
item.closingDate = parseTime(item.closingDate, '{y}-{m}-{d}')
// 修改时间
item.updateTime = parseTime(item.updateTime, '{y}-{m}-{d} {h}:{i}:{s}')
// 动态新增列:门店名称+门店编码+经销山名称(填报模式下,合并到一起)
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
total.value = res.data.total
isLoading.value = false
}
getTableList()
defineExpose({
getTableList
})
/*************** 筛选 ***************/
const showSearch = ref(true)
</script>
<style lang="scss">
// 动态列内容的 render 内样式
// 操作提示列
.operation_tip_cell {
display: flex;
flex-direction: column;
align-items: flex-start;
p {
width: 100%;
margin: 0;
}
p:first-child {
background-color: #e1e2e6;
border-bottom: 1px solid #ebeef5;
}
p:last-child {
padding: 15px 0;
}
}
// 只在填报模式出现的门店名称+门店编码+经销山名称(合并到一起)
.store-name-render-cell {
display: flex;
flex-direction: column;
align-items: flex-start;
p {
width: 100%;
margin: 0;
}
}
</style>
\ No newline at end of file
<template>
<!-- 档期计划 -->
<!-- 隐藏门店搜索 -->
<CustomTable :tableData="tableData"
:baseColumns="baseColumns"
:total="total"
:params="params"
:isLoading="isLoading"
:formatter="formatterFn"
@getTableList="getTableList"
@updateShowSearch="v => showSearch.value = v" />
<!-- 弹窗:实际与计划不一致,让用户补充实际内容 -->
<el-dialog v-model="showPromotionDialog"
title="请输入实际促销机制内容"
width="500%"
:before-close="handleDialogClose">
<el-form :model="dialogForm"
label-width="80px">
<el-form-item label="实际内容">
<el-input v-model="dialogForm.actualPromotionContent"
type="textarea"
:rows="4"
placeholder="请输入实际促销机制内容" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleDialogClose">取消</el-button>
<el-button type="primary"
@click="handleDialogConfirm">确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup
lang="jsx">
import CustomTable from '../components/Table'
import SearchList from '../components/SearchList'
import { getDisplayScheduleList, submitDisplaySchedulePlan } from '@/api'
import { parseTime } from '@/utils'
import { getSchedulePlanConfig } from './data.jsx'
const { proxy } = getCurrentInstance()
/*************** 操作类型 ***************/
const props = defineProps({
params: {
type: Object,
default: () => ({})
}
})
// 提交变更
const submitChange = async (row, col) => {
// 需要特殊处理的
// 实际主货架-形式,为空时,置空实际主货架-数量
if (col.prop === 'actualMainShelfType' && !row.actualMainShelfType) {
row.actualMainShelfQty = 0
}
let requestObj = {}
if (col.requestKey) {
// 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) {
const obj = getSchedulePlanConfig().flatMap(item => {
if (item.children) {
return item.children.filter(child => !child.onlyFill);
}
return [];
}).find(item => item.prop == str)
if (obj && obj.type === 'formula') {
obj.func(row)
}
}
requestObj = col.requestKey.reduce((acc, key) => ({ ...acc, [key]: row[key] }), {})
}
await submitDisplaySchedulePlan({
id: row.sapId,
[col.prop]: row[col.prop], // 当前修改列的值
...requestObj,
actualPromotionFlavor: Array.isArray(row.actualPromotionFlavor) ? row.actualPromotionFlavor.join(',') : '-', // 档期执行-促销口味,为数组时,转成字符串
actualPromotionStartDate: row.actualPromotionStartDate ? parseTime(row.actualPromotionStartDate, '{y}-{m}-{d}') : '', // 档期执行-促销开始日期,为字符串时,转成日期格式
})
}
// 基础列配置
const baseColumns = ref(getSchedulePlanConfig(submitChange));
// 表格数据
const tableData = ref([])
const isLoading = ref(true)
const params = ref({
pageNum: 1,
pageSize: 50,
salesMonth: new Date(),
deptName: '',
dealerCN: '',
lineNameLike: '',
storeCN: ''
})
const total = ref(0)
// 获取表格数据
const getTableList = async () => {
isLoading.value = true
const res = await getDisplayScheduleList({
...props.params,
salesMonth: parseTime(props.params.salesMonth, '{y}-{m}')
})
// 处理日期格式
res.data.rows.forEach(item => {
item.salesMonth = parseTime(item.salesMonth, '{y}-{m}')
item.openingDate = parseTime(item.openingDate, '{y}-{m}-{d}')
item.plannedAdjustmentStartDate = parseTime(item.plannedAdjustmentStartDate, '{y}-{m}-{d}')
item.plannedAdjustmentEndDate = parseTime(item.plannedAdjustmentEndDate, '{y}-{m}-{d}')
item.plannedPromotionStartDate = parseTime(item.plannedPromotionStartDate, '{y}-{m}-{d}')
item.actualPromotionStartDate = parseTime(item.actualPromotionStartDate, '{y}-{m}-{d}')
item.plannedPromotionEndDate = parseTime(item.plannedPromotionEndDate, '{y}-{m}-{d}')
item.actualPromotionEndDate = parseTime(item.actualPromotionEndDate, '{y}-{m}-{d}')
item.totalCostRate = item.totalCostRate ? `${(item.totalCostRate).toFixed(2)}%` : '-'
item.adjustmentCostRatio = item.adjustmentCostRatio ? `${(item.adjustmentCostRatio).toFixed(2)}%` : '-'
// 档期执行-促销口味从字符串转成数组(回显用)
item.actualPromotionFlavor = item.actualPromotionFlavor ? item.actualPromotionFlavor.split(',') : []
// 修改时间
item.updateTime = parseTime(item.updateTime, '{y}-{m}-{d} {h}:{i}:{s}')
})
tableData.value = res.data.rows
total.value = res.data.total
isLoading.value = false
}
defineExpose({
getTableList
})
// 表格格式化指定列的数据内容
const formatterFn = (row, col, cellValue) => {
if (col.prop === 'actualPromotionFlavor') {
// 对数组内容格式化,为空时,显示'-'
return cellValue.length ? cellValue.join(',') : '-'
}
return cellValue
}
onMounted(() => {
getTableList()
})
const showSearch = ref(true)
/*************** 弹窗 ***************/
const showPromotionDialog = ref(false)
const nowEnterRow = ref({}) // 当前弹窗弹出时,要填写哪行的数据对象
const nowEnterRowProp = ref('') // 当前弹窗弹出时,要填写哪行的数据对象的属性
const dialogForm = reactive({
actualPromotionContent: '',
})
// 弹窗关闭时,清空表单数据
const handleDialogClose = () => {
showPromotionDialog.value = false
dialogForm.actualPromotionContent = ''
nowEnterRow.value = {}
nowEnterRowProp.value = ''
}
// 确定
const handleDialogConfirm = async () => {
if (!dialogForm.actualPromotionContent) {
proxy.$modal.msgError('请输入实际促销机制内容')
return
}
// 赋值
nowEnterRow.value[nowEnterRowProp.value] = dialogForm.actualPromotionContent
showPromotionDialog.value = false
}
</script>
<template>
<!-- 档期陈列 -->
<CustomTable :tableData="tableData"
:baseColumns="baseColumns"
:total="total"
:params="params"
:isLoading="isLoading"
@getTableList="getTableList"
@updateShowSearch="v => showSearch.value = v" />
</template>
<script setup
lang="jsx">
import CustomTable from '../components/Table'
import SearchList from '../components/SearchList'
import { getDisplayScheduleDetail, submitDisplayScheduleDetail } from '@/api'
import { parseTime } from '@/utils'
import { getScheduleDisConfig } from './data'
/*************** 表格数据 ***************/
const props = defineProps({
params: {
type: Object,
default: () => ({})
}
})
// 提交变更
const submitChange = async (row, col) => {
// 需要特殊处理的
// 1. 实际主货架-形式,为空时,置空实际主货架-数量
if (col.prop === 'actualMainShelfType' && !row.actualMainShelfType) {
row.actualMainShelfQty = 0
}
// 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) {
const obj = getScheduleDisConfig().flatMap(item => {
if (item.children) {
return item.children.filter(child => !child.onlyFill);
}
return [];
}).find(item => item.prop === str)
if (obj && obj.type === 'formula') {
obj.func(row)
}
}
await submitDisplayScheduleDetail({
id: row.sapdId,
[col.prop]: row[col.prop], // 当前修改列的值
...col.requestKey.reduce((acc, key) => ({ ...acc, [key]: row[key] }), {}), // 额外携带影响的列字段值
// 特殊类型字段处理
// 端架数量实际
actualEndCapQty: row.actualEndCapQty || 0,
// 地堆平米数实际
actualFloorStackArea: row.actualFloorStackArea || 0,
})
}
// 全部列
const baseColumns = ref(getScheduleDisConfig(submitChange));
// 表格数据
const tableData = ref([])
const isLoading = ref(true)
const params = ref({
pageNum: 1,
pageSize: 50,
salesMonth: new Date(),
deptName: '',
dealerCN: '',
lineNameLike: '',
storeCN: ''
})
const total = ref(0)
// 筛选工具
const getTableList = async () => {
isLoading.value = true
const res = await getDisplayScheduleDetail({
...props.params,
salesMonth: parseTime(props.params.salesMonth, '{y}-{m}')
})
res.data.rows.forEach(item => {
// 计划月份
item.salesMonth = parseTime(item.salesMonth, '{y}-{m}')
// 开户日期
item.openingDate = parseTime(item.openingDate, '{y}-{m}-{d}')
// 闭户日期
item.closingDate = parseTime(item.closingDate, '{y}-{m}-{d}')
// 计划 - 档期开始时间
item.plannedPromotionStartDate = parseTime(item.plannedPromotionStartDate, '{y}-{m}-{d}')
// 计划 - 补差开始时间
item.plannedAdjustmentStartDate = parseTime(item.plannedAdjustmentStartDate, '{y}-{m}-{d}')
// 计划 - 档期结束时间
item.plannedPromotionEndDate = parseTime(item.plannedPromotionEndDate, '{y}-{m}-{d}')
// 计划 - 补差结束时间
item.plannedAdjustmentEndDate = parseTime(item.plannedAdjustmentEndDate, '{y}-{m}-{d}')
// 计划 - 档期陈列开始时间
item.plannedPromotionDisplayStartDate = parseTime(item.plannedPromotionDisplayStartDate, '{y}-{m}-{d}')
// 计划 - 档期陈列结束时间
item.plannedPromotionDisplayEndDate = parseTime(item.plannedPromotionDisplayEndDate, '{y}-{m}-{d}')
// 修改时间
item.updateTime = parseTime(item.updateTime, '{y}-{m}-{d} {h}:{i}:{s}')
// 特殊类型处理
// item.actualEndCapQty = item.actualEndCapQty || ''
// item.actualFloorStackArea = item.actualFloorStackArea || ''
})
tableData.value = res.data.rows
total.value = res.data.total
isLoading.value = false
}
getTableList()
defineExpose({
getTableList
})
const showSearch = ref(true)
</script>
\ No newline at end of file
<template>
<!-- 六小金刚 -->
<CustomTable :tableData="tableData"
:baseColumns="baseColumns"
:total="total"
:params="params"
:isLoading="isLoading"
@getTableList="getTableList"
@updateShowSearch="v => showSearch.value = v" />
</template>
<script setup
lang="jsx">
import CustomTable from '../components/Table'
import SearchList from '../components/SearchList'
import { getSixLittleDiamondsPlanList, submitSixLittleDiamondsPlan } from '@/api'
import { getSixLittleDiamondsConfig } from './data.jsx'
import { parseTime } from '@/utils'
const { proxy } = getCurrentInstance()
/*************** 搜索列表 ***************/
const showSearch = ref(true)
/*************** 表格 ***************/
const props = defineProps({
params: {
type: Object,
default: () => ({})
}
})
// 提交变更
const submitChange = async (row, col) => {
let requestObj = {}
if (col.requestKey) {
// 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) {
const obj = getSixLittleDiamondsConfig().flatMap(item => {
if (item.children) {
return item.children.filter(child => !child.onlyFill);
}
return [];
}).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
})
}
// 全部列
const baseColumns = ref(getSixLittleDiamondsConfig(submitChange));
const tableData = ref([])
const isLoading = ref(false)
const total = ref(0)
const params = ref({
pageNum: 1,
pageSize: 50,
salesMonth: new Date(),
deptName: '',
dealerCN: '',
lineNameLike: '',
storeCN: ''
})
const getTableList = () => {
isLoading.value = true
getSixLittleDiamondsPlanList({
...props.params,
salesMonth: proxy.parseTime(props.params.salesMonth, '{y}-{m}')
}).then(res => {
isLoading.value = false
tableData.value = res.data.rows.map(item => {
// 计划月份
item.salesMonth = proxy.parseTime(item.salesMonth, '{y}-{m}')
return item
}) || []
total.value = res.data.total || 0
})
}
getTableList()
defineExpose({
getTableList
})
</script>
<style scoped></style>
\ No newline at end of file
<template>
<!-- 零食陈列 -->
<CustomTable :tableData="tableData"
:baseColumns="baseColumns"
:total="total"
:params="params"
:isLoading="isLoading"
@getTableList="getTableList"
@updateShowSearch="v => showSearch.value = v" />
</template>
<script setup
lang="jsx">
import CustomTable from '../components/Table'
import SearchList from '../components/SearchList'
import { getSnackPlanList, submitSnackPlan } from '@/api'
import { parseTime } from '@/utils'
import { getSnackCofing } from './data.jsx';
/*************** 操作类型 ***************/
const props = defineProps({
params: {
type: Object,
default: () => ({})
}
})
// 提交变更
const submitChange = async (row, col) => {
// 需要特殊处理的
// 1. 实际主货架-形式,为空时,置空实际主货架-数量
// if (col.prop === 'actualMainShelfType' && !row.actualMainShelfType) {
// row.actualMainShelfQty = 0
// }
// 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) {
const obj = getSnackCofing().flatMap(item => {
if (item.children) {
return item.children.filter(child => !child.onlyFill);
}
return [];
}).find(item => item.prop === str)
if (obj && obj.type === 'formula') {
obj.func(row)
}
}
await submitSnackPlan({
id: row.sasdId,
[col.prop]: row[col.prop], // 当前修改列的值
...col.requestKey.reduce((acc, key) => ({ ...acc, [key]: row[key] }), {}), // 额外携带影响的列字段值
})
// // 这些提交变化的,同时会影响公式计算的,需要把公式计算的字段列结果,一起发给后台
// // 注意:实际主货架形式改变并为空时,要置空实际主货架数量
// if (col.prop === 'actualMainShelfType' && !row.actualMainShelfType) {
// row.actualMainShelfQty = ''
// // 并提交保存一次
// await submitDisplayPlan({
// id: row.sadId,
// actualMainShelfQty: row.actualMainShelfQty,
// })
// }
// // 主货架
// if (['actualMainShelfType', 'actualMainShelfQty'].includes(col.prop)) {
// await submitDisplayPlan({
// id: row.sadId,
// actualMainShelfExecuted: row.actualMainShelfExecuted,
// })
// }
// // 端架
// if (['actualEndCapQty'].includes(col.prop)) {
// await submitDisplayPlan({
// id: row.sadId,
// actualEndCapExecuted: row.actualEndCapExecuted,
// })
// }
// // 地堆
// if (['actualFloorStackArea', 'actualFloorStackQty'].includes(col.prop)) {
// await submitDisplayPlan({
// id: row.sadId,
// actualFloorStackExecuted: row.actualFloorStackExecuted,
// })
// }
// // 常规陈列是否执行
// await submitDisplayPlan({
// id: row.sadId,
// regularDisplayExecuted: row.regularDisplayExecuted,
// })
}
// 全部列
const baseColumns = ref(getSnackCofing(submitChange));
// 表格数据
const tableData = ref([])
const isLoading = ref(true)
const params = ref({
pageNum: 1,
pageSize: 50,
salesMonth: new Date(),
deptName: '',
dealerCN: '',
lineNameLike: '',
storeCN: ''
})
const total = ref(0)
// 筛选工具
const getTableList = async () => {
isLoading.value = true
const res = await getSnackPlanList({
...props.params,
salesMonth: parseTime(props.params.salesMonth, '{y}-{m}')
})
res.data.rows.forEach(item => {
// 计划月份
item.salesMonth = parseTime(item.salesMonth, '{y}-{m}')
// 开户日期
item.openingDate = parseTime(item.openingDate, '{y}-{m}-{d}')
// 闭户日期
item.closingDate = parseTime(item.closingDate, '{y}-{m}-{d}')
// 修改时间
item.updateTime = parseTime(item.updateTime, '{y}-{m}-{d} {h}:{i}:{s}')
})
tableData.value = res.data.rows
total.value = res.data.total
isLoading.value = false
}
getTableList()
defineExpose({
getTableList
})
const showSearch = ref(true)
</script>
<style scoped
lang="scss">
.container {
.el-tabs {
height: 100%;
display: flex;
flex-direction: column-reverse;
.el-tabs__content {
display: flex;
flex-direction: column;
height: 100%;
.el-tab-pane {
height: 100%;
display: flex;
flex-direction: column;
.pagination-container {
margin: 10px;
}
}
.formula-column {
display: flex;
justify-content: center;
align-items: center;
.el-icon {
margin-left: 2px;
}
}
}
}
.el-form-item {
align-items: center;
}
/* 表格区域 */
.auto-fit-header-table {
/* 优化超长文本的显示效果 */
.cell {
/* padding: 0 .2133rem; */
}
::v-deep(.el-table__row) {
.el-table__cell {
padding: 0;
}
}
::v-deep(.column-style) {
.cell-style {
/* margin: 0 -12px; */
>div {
display: flex;
flex-direction: column;
align-items: flex-start;
>span {
text-align: left;
text-indent: 5px;
display: inline-block;
width: 100%;
background-color: #e1e2e6;
border-bottom: 1px solid #ebeef5;
}
}
/* 表格内下拉框 */
.el-select {
width: 100% !important;
padding: 10px;
}
.el-input {
padding: 10px;
}
}
}
}
}
</style>
\ No newline at end of file
<template>
<!-- 三米两秒 -->
<CustomTable :tableData="tableData"
:baseColumns="baseColumns"
:total="total"
:params="params"
:isLoading="isLoading"
@getTableList="getTableList"
@updateShowSearch="v => showSearch.value = v" />
</template>
<script setup
lang="jsx">
import CustomTable from '../components/Table'
import { getThreeMetersTwoSecondsPlanList, submitThreeMetersTwoSecondsPlan } from '@/api'
import { getThreeTwoSecondsConfig } from './data.jsx'
const { proxy } = getCurrentInstance()
/*************** 搜索列表 ***************/
const showSearch = ref(true)
/*************** 表格 ***************/
const props = defineProps({
params: {
type: Object,
default: () => ({})
}
})
// 提交变更
const submitChange = async (row, col) => {
let requestObj = {}
if (col.requestKey) {
// 关联的公式计算列,需要特殊处理
for (const str of col.requestKey) {
const obj = getThreeTwoSecondsConfig().flatMap(item => {
if (item.children) {
return item.children.filter(child => !child.onlyFill);
}
return [];
}).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
})
}
// 全部列
const baseColumns = ref(getThreeTwoSecondsConfig(submitChange));
const tableData = ref([])
const isLoading = ref(false)
const total = ref(0)
const params = ref({
pageNum: 1,
pageSize: 50,
salesMonth: new Date(),
deptName: '',
dealerCN: '',
lineNameLike: '',
storeCN: ''
})
const getTableList = () => {
isLoading.value = true
getThreeMetersTwoSecondsPlanList({
...params.value,
salesMonth: proxy.parseTime(params.value.salesMonth, '{y}-{m}')
}).then(res => {
isLoading.value = false
tableData.value = res.data.rows.map(item => {
// 计划月份
item.salesMonth = proxy.parseTime(item.salesMonth, '{y}-{m}')
return item
}) || []
total.value = res.data.total || 0
})
}
getTableList()
defineExpose({
getTableList
})
</script>
<style scoped></style>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论