提交 410b7509 authored 作者: lidongxu's avatar lidongxu

Merge branch 'depart_change'

......@@ -48,6 +48,7 @@
"devDependencies": {
"@vant/auto-import-resolver": "^1.3.0",
"@vitejs/plugin-vue": "5.0.5",
"@vitejs/plugin-vue-jsx": "^5.1.1",
"cz-conventional-changelog": "^3.3.0",
"postcss-pxtorem": "^6.1.0",
"sass": "1.77.5",
......
......@@ -26,6 +26,7 @@ export * from './monitor/job'
export * from './monitor/jobLog'
export * from './monitor/online'
export * from './monitor/server'
export * from './promotion/display_schedule'
export * from './promotion/plan'
export * from './promotion/task'
export * from './scm/logistics_receipt'
......
import request from '@/utils/request';
// 获取-陈列计划列表
export function getDisplayList(params) {
return request({
url: '/operation/sales/ap_display/query/page',
params
})
}
// 填报-陈列计划
export function submitDisplayPlan(data) {
// 遍历 data 每对 key value,发现 value 是 undefined 替换成空字符串
Object.keys(data).forEach(key => {
if (data[key] === undefined || data[key] === null) {
data[key] = ''
}
})
return request({
url: `/operation/sales/ap_display/core/${data.id}`,
method: 'PUT',
data: {
display: {
...data,
id: undefined // 不携带 id
}
}
});
}
// 获取档期计划列表
export function getDisplayScheduleList(params) {
return request({
url: '/operation/sales/ap_promotion/query/page',
params
})
}
// 填报-档期计划
export function submitDisplaySchedulePlan(data) {
Object.keys(data).forEach(key => {
if (data[key] === undefined || data[key] === null) {
data[key] = ''
}
})
return request({
url: `/operation/sales/ap_promotion/core/${data.id}`,
method: 'PUT',
data: {
promotion: data
}
});
}
// 获取-档期陈列列表
export function getDisplayScheduleDetail(params) {
return request({
url: '/operation/sales/ap_display/query/pro_page',
params
})
}
// 填报-档期陈列
export function submitDisplayScheduleDetail(data) {
Object.keys(data).forEach(key => {
if (data[key] === undefined || data[key] === null) {
data[key] = ''
}
})
return request({
url: `/operation/sales/ap_display/core_pro/${data.id}`,
method: 'PUT',
data: {
promotionDisplay: {
...data,
id: undefined // 不携带 id
}
}
});
}
// 获取-零食计划列表
export function getSnackPlanList(params) {
return request({
url: '/operation/sales/ap_display/query/snack_page',
params
})
}
// 填报-零食计划
export function submitSnackPlan(data) {
Object.keys(data).forEach(key => {
if (data[key] === undefined || data[key] === null) {
data[key] = ''
}
})
return request({
url: `/operation/sales/ap_display/core_snack/${data.id}`,
method: 'PUT',
data: {
snackDisplay: {
...data,
id: undefined // 不携带 id
}
}
});
}
\ No newline at end of file
......@@ -126,7 +126,7 @@ aside {
// 全局样式
// 容器
.app-container {
padding: 30px;
padding: 20px;
height: 100%;
// 内容区域
......
......@@ -19,7 +19,7 @@
icon="Refresh"
@click="refresh()" />
</el-tooltip>
<el-tooltip class="item"
<el-tooltip class="item "
effect="dark"
content="显隐列"
placement="top"
......@@ -31,7 +31,8 @@
<el-dropdown trigger="click"
:hide-on-click="false"
style="padding-left: 12px"
v-if="showColumnsType == 'checkbox'">
v-if="showColumnsType == 'checkbox'"
class="columns-dropdown">
<el-button circle
icon="Menu" />
<template #dropdown>
......@@ -39,14 +40,36 @@
<template v-for="item in columns"
:key="item.key">
<el-dropdown-item>
<el-checkbox :checked="item.visible"
@change="checkboxChange($event, item.label)"
<el-checkbox v-model="item.visible"
:label="item.label" />
</el-dropdown-item>
</template>
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- 树结构选择方式 -->
<el-dropdown trigger="click"
:hide-on-click="false"
style="padding-left: 12px"
v-if="showColumnsType == 'tree'"
>
<el-button circle
icon="Menu" />
<template #dropdown>
<el-dropdown-menu class="tree-dropdown-menu">
<el-tree
:data="treeData"
show-checkbox
node-key="prop"
ref="columnTree"
:props="treeProps"
default-expand-all
check-on-click-node
:default-checked-keys="defaultCheckedKeys"
@check="handleTreeCheck" />
</el-dropdown-menu>
</template>
</el-dropdown>
</el-tooltip>
<!-- 自定义功能 -->
<slot></slot>
......@@ -79,7 +102,7 @@ const props = defineProps({
type: Boolean,
default: true,
},
/* 显隐列类型(transfer穿梭框、checkbox复选框) */
/* 显隐列类型(transfer穿梭框、checkbox复选框、tree树形结构) */
showColumnsType: {
type: String,
default: "checkbox",
......@@ -89,6 +112,19 @@ const props = defineProps({
type: Number,
default: 10,
},
/* 树形结构配置(可选) */
treeConfig: {
type: Object,
default: () => ({
labelKey: 'label',
childrenKey: 'children'
})
},
/* 树形结构默认选中的节点 */
defaultCheckedKeys: {
type: Array,
default: () => []
}
})
const emits = defineEmits(['update:showSearch', 'queryTable']);
......@@ -99,6 +135,15 @@ const value = ref([]);
const title = ref("显示/隐藏");
// 是否显示弹出层
const open = ref(false);
// 树结构数据
const treeData = ref([]);
// 树结构配置
const treeProps = {
label: props.treeConfig.labelKey || 'label',
children: props.treeConfig.childrenKey || 'children'
};
// 树引用
const columnTree = ref(null);
const style = computed(() => {
const ret = {};
......@@ -131,6 +176,82 @@ function showColumn() {
open.value = true;
}
// 树形结构节点勾选变化
function handleTreeCheck(currentNode, checkedInfo) {
const node = currentNode;
// 判断当前节点是否被勾选(检查当前节点prop是否在checkedKeys中)
const isChecked = checkedInfo.checkedKeys.includes(node.prop);
// 处理父节点的特殊情况:如果是父节点(有children),则更新其所有子节点
if (node.children && node.children.length > 0) {
// 获取该父节点下的所有子节点prop
const allChildProps = [];
const collectChildProps = (nodes) => {
nodes.forEach(child => {
if (child.children && child.children.length > 0) {
collectChildProps(child.children);
} else {
allChildProps.push(child.prop);
}
});
};
collectChildProps(node.children);
// 更新所有子节点的visible属性
allChildProps.forEach(prop => {
updateColumnVisibilityByProp(prop, isChecked);
});
} else {
// 对于单个节点,直接更新其visible属性
updateColumnVisibilityByProp(node.prop, isChecked);
}
}
// 新增:根据prop更新对应列的visible属性
function updateColumnVisibilityByProp(prop, visible) {
const updateColumn = (columns) => {
for (let i = 0; i < columns.length; i++) {
const item = columns[i];
if (item.children && item.children.length > 0) {
// 递归查找子节点
const found = updateColumn(item.children);
if (found) return true;
} else if (item.prop === prop) {
// 找到匹配的节点,更新其visible属性
item.visible = visible;
return true;
}
}
return false;
};
// 从props.columns中查找并更新
updateColumn(props.columns);
}
// 3. 修改initTreeData方法,确保正确处理prop字段
function initTreeData() {
if (props.columns && props.showColumnsType === 'tree') {
// 创建树数据但不深拷贝,保持引用关系
treeData.value = props.columns;
}
}
watch(() => props.columns, () => {
if (props.showColumnsType === 'tree') {
initTreeData();
}
})
// 再添加一个watcher来监听showColumnsType变化
watch(
() => props.showColumnsType,
(newType, oldType) => {
if (newType === 'tree') {
initTreeData();
}
}
)
if (props.showColumnsType == 'transfer') {
// 显隐列初始默认隐藏列
for (let item in props.columns) {
......@@ -138,13 +259,19 @@ if (props.showColumnsType == 'transfer') {
value.value.push(parseInt(item));
}
}
} else if (props.showColumnsType === 'tree') {
// 初始化树数据
initTreeData();
}
// 勾选
function checkboxChange(event, label) {
props.columns.filter(item => item.label == label)[0].visible = event;
}
// 勾选 - 修改为更健壮的实现
// function checkboxChange(event, label) {
// // 使用find方法而不是filter,更高效
// const column = props.columns.find(item => item.label === label);
// if (column) {
// column.visible = event;
// }
// }
</script>
<style lang='scss'
......@@ -174,4 +301,25 @@ function checkboxChange(event, label) {
}
}
/* 树结构下拉菜单样式 */
.tree-dropdown-menu {
max-height: 400px;
overflow-y: auto;
min-width: 300px;
padding: 10px;
}
:deep(.el-tree) {
max-height: 380px;
overflow-y: auto;
}
/* 添加选中节点文字蓝色样式 */
:deep(.el-tree-node.is-checked > .el-tree-node__content .el-tree-node__label) {
color: #409EFF;
}
:deep(.el-tree-node__content .el-checkbox__input.is-checked + .el-checkbox__label) {
color: #409EFF;
}
</style>
\ No newline at end of file
......@@ -69,7 +69,7 @@ import VConsole from 'vconsole'
// 创建vConsole实例
// 生产环境不加载
if (import.meta.env.VITE_APP_ENV !== 'production') {
if (import.meta.env.VITE_APP_ENV === 'staging') {
new VConsole()
}
......
......@@ -50,10 +50,7 @@ router.beforeEach((to, from, next) => {
})
})
}).catch(err => {
useUserStore().logOut().then(() => {
ElMessage.error(err)
next({ path: '/' })
})
})
} else {
next()
......
......@@ -7,6 +7,7 @@ import cache from '@/plugins/cache'
import { saveAs } from 'file-saver'
import useUserStore from '@/store/modules/user'
import { fsClientAuth } from '@/api'
import router from '@/router'
let downloadLoadingInstance;
// 是否显示重新登录
......@@ -103,14 +104,15 @@ service.interceptors.response.use(async res => {
// PC/移动端刷新 token 有效期
// await useUserStore().refreshTokenFn()
// return service(res.config)
// ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
// isRelogin.show = false;
// useUserStore().logOut().then(() => {
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
isRelogin.show = false;
useUserStore().logOut().then(() => {
// location.href = '#/login';
// })
// }).catch(() => {
// isRelogin.show = false;
// });
router.push({ path: '/login' })
})
}).catch(() => {
isRelogin.show = false;
});
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
} else if (code === 403) {
......
<template>
<div class="app-container">
<div class="container">
卤币明细
</div>
</div>
</template>
<script setup>
</script>
<style scoped
lang="scss"></style>
\ No newline at end of file
<template>
<el-row>
<el-form-item>
<el-radio-group v-model="operation"
@change="checkTableColumns">
<el-radio-button label="展示模式"
value="展示模式" />
<el-radio-button label="填报模式"
value="填报模式" />
</el-radio-group>
</el-form-item>
<right-toolbar v-model:showSearch="showSearch"
@queryTable="getTableList()"
:columns="chooseColumns"
:showColumnsType="operation === '展示模式' ? 'tree' : 'checkbox'"
:defaultCheckedKeys="visibleProps">
</right-toolbar>
</el-row>
<!-- 筛选列组的按钮 -->
<el-table :data="tableData"
border
ref="tableRef"
class="auto-fit-header-table "
:class="{ 'cell-no-padding': operation === '填报模式' }"
v-loading="isLoading">
<template v-for="col in tableColumns">
<el-table-column v-if="col.visible"
:label="col.label"
:key="col.prop"
align="center"
:show-overflow-tooltip="col.showOverflowTooltip"
class-name="column-style"
:width="col.width"
:fixed="operation === '填报模式' && col.fixed">
<template #header="{ column }">
<!-- 只为特定公式列添加问号图标 -->
<span class="formula-column">
{{ column.label }}
<el-tooltip v-if="col.type === 'formula'"
:content="col.formulaStr"
placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</span>
</template>
<template #default="{ row }">
<!-- 填报模式 -->
<div v-if="operation === '填报模式'"
style="overflow: visible !important;">
<!-- 带选择框/输入框的操作单元格 -->
<div v-if="col.render"
class="cell-style">
<component :is="col.render(h, row, col)" />
</div>
<!-- 正常显示/公式计算 -->
<div v-else-if="col.type === 'formula'">
{{ col.func(row) }}
</div>
<div v-else
class="overflow_wrap">
<!-- 超出部分显示省略号 -->
<span>
{{ row[col.prop] || '-' }}
</span>
</div>
</div>
<!-- 展示模式 -->
<div v-else>{{ row[col.prop] || '-' }}</div>
</template>
</el-table-column>
</template>
</el-table>
<!-- 分页 -->
<pagination :total="total"
v-model:page="params.pageNum"
v-model:limit="params.pageSize"
@pagination="getTableList" />
</template>
<script setup>
import { h } from 'vue'
/*************** 操作类型 ***************/
const operation = ref('展示模式');
// const operation = ref('填报模式');
const tableRef = ref(null)
const props = defineProps({
tableColumns: { // 表格列
type: Array,
default: () => []
},
chooseColumns: { // 右上角工具选择列
type: Array,
default: () => []
},
visibleProps: { // 右上角工具为树结构,默认勾选的列
type: Array,
default: () => []
},
tableData: { // 数据源
type: Array,
default: () => []
},
isLoading: {
type: Boolean,
default: false
},
total: {
type: Number,
default: 0
},
params: {
type: Object,
default: () => ({
pageNum: 1,
pageSize: 10,
})
}
})
const emit = defineEmits(['updateColumns', 'getTableList', 'updateShowSearch'])
// 右上角工具
const showSearch = ref(true)
// 切换平铺/填报模式
const checkTableColumns = async () => {
// 通知外面传入 tableColumns / chooseColumns 数据源
await emit('updateColumns', operation.value)
// 强制表格立即应用所有宽度设置,避免先自适应再调整
nextTick(() => {
if (tableRef.value) {
tableRef.value.doLayout()
}
})
}
onMounted(() => {
checkTableColumns()
})
const getTableList = () => {
emit('getTableList')
}
watch(showSearch, (newVal) => {
emit('updateShowSearch', newVal)
})
</script>
<style scoped
lang="scss">
.container {
.el-tabs {
height: 100%;
display: flex;
flex-direction: column-reverse;
.el-tabs__content {
display: flex;
flex-direction: column;
height: 100%;
.el-tab-pane {
height: 100%;
display: flex;
flex-direction: column;
.pagination-container {
margin: 10px;
}
}
.formula-column {
display: flex;
justify-content: center;
align-items: center;
.el-icon {
margin-left: 2px;
}
}
}
}
.el-form-item {
align-items: center;
}
/* 表格区域 */
.auto-fit-header-table {
/* 优化超长文本的显示效果 */
.cell {
/* padding: 0 .2133rem; */
>div {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
&.cell-no-padding {
::v-deep(.el-table__row) {
.el-table__cell {
padding: 0;
}
}
}
::v-deep(.column-style) {
.cell {
/* overflow: visible; */
>div {
/* overflow: visible !important; */
}
.cell-style {
margin: 0 -12px;
>div {
display: flex;
flex-direction: column;
align-items: flex-start;
>span {
text-align: left;
text-indent: 5px;
display: inline-block;
width: 100%;
background-color: #e1e2e6;
border-bottom: 1px solid #ebeef5;
}
}
/* 表格内下拉框 */
.el-select {
width: 100% !important;
padding: 10px;
}
.el-input {
padding: 10px;
}
.date-picker {
width: 100%;
padding: 10px;
.el-input {
width: 100%;
padding: 0;
}
}
}
}
}
}
}
/* 特殊动态渲染单元格样式 */
.render-cell {
p {
margin: 0;
}
}
/* 操作提示列 */
.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;
}
}
/* 超出部分显示省略号 */
.overflow_wrap {
width: 100%;
/* 固定宽度 */
white-space: nowrap;
/* 不换行 */
overflow: hidden;
/* 隐藏超出部分 */
text-overflow: ellipsis;
/* 显示省略号 */
display: inline-block;
}
</style>
\ No newline at end of file
<template>
<div class="search" v-show="showSearch">
<el-form :inline="true"
:model="queryParams"
class="demo-form-inline">
<el-form-item label="计划月份">
<el-date-picker v-model="queryParams.salesMonth"
type="month"
placeholder="选择计划月份"
@change="handleChange"
clearable />
</el-form-item>
<el-form-item label="大区/战区">
<el-input v-model="queryParams.deptName"
placeholder="请输入大区/战区"
@input="handleChange"
clearable />
</el-form-item>
<el-form-item label="经销商编码/名称">
<el-input v-model="queryParams.dealerCN"
placeholder="请输入经销商编码/名称"
@input="handleChange"
clearable />
</el-form-item>
<el-form-item label="系统名称">
<el-input v-model="queryParams.lineNameLike"
placeholder="请输入系统名称"
@input="handleChange"
clearable />
</el-form-item>
<el-form-item label="门店编码/名称">
<el-input v-model="queryParams.storeCN"
placeholder="请输入门店编码/名称"
@input="handleChange"
clearable />
</el-form-item>
</el-form>
</div>
</template>
<script setup>
const props = defineProps({
showSearch: {
type: Boolean,
default: true
}
})
const queryParams = reactive({
salesMonth: '',
deptName: '',
dealerCN: '',
lineNameLike: '',
storeCN: ''
})
const emits = defineEmits(['change'])
const handleChange = () => {
emits('change', queryParams)
}
</script>
<style scoped
lang="scss"></style>
\ No newline at end of file
<template>
<div class="app-container">
<div class="container">
<el-tabs v-model="activeName"
class="demo-tabs"
@tab-click="handleClickTabs">
<el-tab-pane label="常规陈列"
name="常规陈列">
<Display />
</el-tab-pane>
<el-tab-pane label="档期计划"
name="档期计划">
<Schedule />
</el-tab-pane>
<el-tab-pane label="档期陈列"
name="档期陈列">
<ScheduleDis />
</el-tab-pane>
<el-tab-pane label="零食陈列"
name="零食陈列">
<Snack />
</el-tab-pane>
</el-tabs>
</div>
</div>
</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'
const activeName = ref('常规陈列');
const handleClickTabs = (tab) => {
activeName.value = tab.name;
}
</script>
<style scoped
lang="scss">
.app-container {
padding: 10px;
.container {
padding: 10px;
.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;
}
.formula-column {
display: flex;
justify-content: center;
align-items: center;
.el-icon {
margin-left: 2px;
}
}
}
}
.el-form-item {
align-items: center;
}
}
}
</style>
\ No newline at end of file
......@@ -197,7 +197,7 @@ const columns = ref([
{
label: '快递单号',
prop: 'expressNo',
width: 150
width: 220
},
{
label: 'DD 单号',
......
......@@ -63,6 +63,9 @@
<el-table-column prop="deptName"
label="部门名称"
width="260"></el-table-column>
<el-table-column prop="deptCode"
label="部门编码"
width="200"></el-table-column>
<el-table-column prop="orderNum"
label="排序"
width="200"></el-table-column>
......@@ -110,7 +113,8 @@
<el-dialog :title="title"
v-model="open"
append-to-body
draggable overflow>
draggable
overflow>
<el-form ref="deptRef"
:model="form"
:rules="rules"
......@@ -135,6 +139,14 @@
placeholder="请输入部门名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门编码"
prop="deptCode">
<el-input v-model="form.deptCode"
placeholder="请输入 WB 字符开头数字字母组合字符"
maxlength="20" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="显示排序"
prop="orderNum">
......@@ -176,6 +188,7 @@
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
......@@ -216,9 +229,23 @@
rules: {
parentId: [{ required: true, message: "上级部门不能为空", trigger: "blur" }],
deptName: [{ required: true, message: "部门名称不能为空", trigger: "blur" }],
deptCode: [{ required: true, message: "部门编码不能为空", trigger: "blur" }, {
min: 3, max: 10, message: "部门编码长度必须在 3-10 位之间", trigger: "blur"
},
{
validator: (rule, value, callback) => {
// 部门编码只能是 WB 字符开头数字字母组合的 1-10 位字符
if (!/^WB[0-9a-zA-Z-_]{1,8}$/.test(value)) {
callback(new Error("部门编码只能是 WB 字符开头,数字字母组合的字符"));
} else {
callback();
}
}, trigger: "blur"
}],
orderNum: [{ required: true, message: "显示排序不能为空", trigger: "blur" }],
email: [{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }]
phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }],
},
});
......
......@@ -3,6 +3,7 @@ import pxtorem from 'postcss-pxtorem'
import { defineConfig, loadEnv } from 'vite'
import createVitePlugins from './vite/plugins'
export default defineConfig(({ mode, command }) => {
const env = loadEnv(mode, process.cwd())
const { VITE_APP_PUBLIC_PATH } = env
......
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论