提交 71b6af3b authored 作者: lidongxu's avatar lidongxu

fix(levitatedsphere): 修复悬浮球工具的 bug

抬起事件应该在悬浮球上不应该在 window 上,防止切换导致 up 事件提前触发找不到悬浮球标签导致报错
上级 e3da9762
......@@ -25,13 +25,14 @@
"element-plus": "2.7.6",
"file-saver": "2.0.5",
"fuse.js": "6.6.2",
"gsap": "^3.12.5",
"js-beautify": "1.14.11",
"js-cookie": "3.0.5",
"jsencrypt": "3.3.2",
"nprogress": "0.2.0",
"pinia": "2.1.7",
"splitpanes": "3.1.5",
"vue": "3.4.31",
"vue": "^3.5.13",
"vue-count-to": "^1.0.13",
"vue-cropper": "1.1.1",
"vue-router": "4.4.0",
......
......@@ -84,6 +84,7 @@ $--color-info: #909399;
html {
--el-gray-1: rgba(0, 0, 0, 0.45);
--el-gray-2: #666666;
--el-gray-3: rgb(235, 235, 235);
/* 主页背景 */
.app-main {
......@@ -95,6 +96,8 @@ html {
html.dark {
--el-gray-1: white;
--el-gray-2: white;
--el-gray-3: #1d1e1f;
/* 默认通用 */
--el-bg-color: #141414;
......
......@@ -65,40 +65,34 @@ resize()
onMounted(() => {
window.addEventListener('resize', resize)
nextTick(() => {
let isDown = false
const mousemove = (e) => {
e.preventDefault();
let touch = e;
// 限制 left 和 top 的值,使其不会超出可视区域
left.value = Math.max(props.gapWidth, Math.min(touch.clientX - 20, clientWidth.value - props.itemWidth - props.gapWidth));
top.value = Math.max(0, Math.min(touch.clientY - 25, clientHeight.value - props.itemHeight));
}
levitatedSphere.value.addEventListener('mousedown', (e) => {
e.preventDefault();
levitatedSphere.value.style.transition = 'none';
downPoint.x = e.clientX
downPoint.y = e.clientY
isDown = true
window.addEventListener('mousemove', mousemove);
})
// 在拖拽过程中,组件应该跟随手指的移动而移动
window.addEventListener('mousemove', (e) => {
e.preventDefault();
if (isDown) {
let touch = e;
// 限制 left 和 top 的值,使其不会超出可视区域
left.value = Math.max(props.gapWidth, Math.min(touch.clientX - 20, clientWidth.value - props.itemWidth - props.gapWidth));
top.value = Math.max(0, Math.min(touch.clientY - 25, clientHeight.value - props.itemHeight));
}
});
// 拖拽结束后,重新调整组件的位置并重新设置过渡动画
window.addEventListener('mouseup', (e) => {
levitatedSphere.value.addEventListener('mouseup', e => {
levitatedSphere.value.style.transition = 'all 0.5s';
if (left.value > document.documentElement.clientWidth / 2) {
left.value = document.documentElement.clientWidth - props.itemWidth - props.gapWidth;
} else {
left.value = props.gapWidth;
}
isDown = false
upPoint.x = e.clientX
upPoint.y = e.clientY
window.removeEventListener('mousemove', mousemove);
})
});
})
onBeforeUnmount(() => {
window.removeEventListener('resize', resize)
})
......
<template>
<div class="top-right-btn" :style="style">
<div class="top-right-btn"
:style="style">
<el-row>
<el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search">
<el-button circle icon="Search" @click="toggleSearch()" />
<el-tooltip class="item"
effect="dark"
:content="showSearch ? '隐藏搜索' : '显示搜索'"
placement="top"
v-if="search">
<el-button circle
icon="Search"
@click="toggleSearch()" />
</el-tooltip>
<el-tooltip class="item" effect="dark" content="刷新" placement="top">
<el-button circle icon="Refresh" @click="refresh()" />
<el-tooltip class="item"
effect="dark"
content="刷新"
placement="top">
<el-button circle
icon="Refresh"
@click="refresh()" />
</el-tooltip>
<el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columns">
<el-button circle icon="Menu" @click="showColumn()" v-if="showColumnsType == 'transfer'"/>
<el-dropdown trigger="click" :hide-on-click="false" style="padding-left: 12px" v-if="showColumnsType == 'checkbox'">
<el-button circle icon="Menu" />
<el-tooltip class="item"
effect="dark"
content="显隐列"
placement="top"
v-if="columns">
<el-button circle
icon="Menu"
@click="showColumn()"
v-if="showColumnsType == 'transfer'" />
<el-dropdown trigger="click"
:hide-on-click="false"
style="padding-left: 12px"
v-if="showColumnsType == 'checkbox'">
<el-button circle
icon="Menu" />
<template #dropdown>
<el-dropdown-menu>
<template v-for="item in columns" :key="item.key">
<template v-for="item in columns"
:key="item.key">
<el-dropdown-item>
<el-checkbox :checked="item.visible" @change="checkboxChange($event, item.label)" :label="item.label" />
<el-checkbox :checked="item.visible"
@change="checkboxChange($event, item.label)"
:label="item.label" />
</el-dropdown-item>
</template>
</el-dropdown-menu>
......@@ -25,13 +51,13 @@
<!-- 自定义功能 -->
<slot></slot>
</el-row>
<el-dialog :title="title" v-model="open" append-to-body>
<el-transfer
:titles="['显示', '隐藏']"
<el-dialog :title="title"
v-model="open"
append-to-body>
<el-transfer :titles="['显示', '隐藏']"
v-model="value"
:data="columns"
@change="dataChange"
></el-transfer>
@change="dataChange"></el-transfer>
</el-dialog>
</div>
</template>
......@@ -120,17 +146,31 @@ function checkboxChange(event, label) {
</script>
<style lang='scss' scoped>
:deep(.el-transfer__button) {
border-radius: 50%;
display: block;
margin-left: 0px;
}
:deep(.el-transfer__button:first-child) {
margin-bottom: 10px;
}
:deep(.el-dropdown-menu__item) {
line-height: 30px;
padding: 0 17px;
}
<style lang='scss'
scoped>
:deep(.el-transfer__button) {
border-radius: 50%;
display: block;
margin-left: 0px;
}
:deep(.el-transfer__button:first-child) {
margin-bottom: 10px;
}
:deep(.el-dropdown-menu__item) {
line-height: 30px;
padding: 0 17px;
}
:deep(.el-button) {
margin-left: 12px;
}
.el-dropdown {
.el-button {
margin-left: 0;
}
}
</style>
// 表单在浏览器控制台的异常处理
// 处理控制台错误异常-对业务没影响就是不好看
// 错误如此:Blocked aria-hidden on an element because its descendant retained focus. ......
export default {
bind(el, binding) {
const ariaEls = el.querySelectorAll('.el-radio__original')
ariaEls.forEach((item) => {
item.removeAttribute('aria-hidden')
})
}
}
import hasRole from './permission/hasRole'
import hasPermi from './permission/hasPermi'
import copyText from './common/copyText'
import formError from './form/error'
export default function directive(app){
app.directive('hasRole', hasRole)
app.directive('hasPermi', hasPermi)
app.directive('copyText', copyText)
}
\ No newline at end of file
app.directive('removeConsoleError', formError)
}
......@@ -418,4 +418,21 @@ export function resetObjValue(obj, props) {
}
}
}
}
/**
* 根据字符串属性路径访问对象里的值
* @param {*} obj
* @param {*} path
* @returns
*/
export function getObjValueByPath(obj, path) {
const pathArr = path?.split('.')
let result = obj
for (let i = 0; i < pathArr?.length; i++) {
result = result[pathArr[i]]
}
if (result === null) return 0
return result
}
\ No newline at end of file
......@@ -28,9 +28,11 @@ export function divSafe(arg1, arg2) {
* @returns {string} 格式化后的字符串
*/
export function formatNumberWithUnit(value, extraDescription, bool, round) {
if (typeof value !== 'number') {
throw new Error('输入值必须是数字');
}
// if (typeof value !== 'number') {
// throw new Error('输入值必须是数字');
// }
// 如果 value 是空直接返回 0
if (value === 0 || value === '' || value === null || value === undefined) return 0;
// 不转换
if (!bool) {
......
......@@ -71,7 +71,7 @@
<script setup>
import GradientArea from './GradientArea.vue';
import TableList from './TableList.vue';
import { getComPrdListAPI } from '@/api'
import { getComPrdListAPI, getSycmListAPI } from '@/api'
import { generatorDayList, parseTime, getBrandColor, resetObjValue } from '@/utils'
import { useDatePickerOptions } from '@/hooks'
......@@ -328,11 +328,10 @@ const filterTableData = () => {
// 获取数据
const getList = async () => {
loading.value = true
const { data } = await getSycmStoreListAPI({
const { data } = await getSycmListAPI({
startDate: parseTime(queryParams.date[0], '{y}-{m}-{d}'),
endDate: parseTime(queryParams.date[1], '{y}-{m}-{d}'),
platformStore: queryParams.brandList.join(','),
prdId: queryParams.prdList.join(','),
dataType: queryParams.typeList.join(',')
})
......@@ -358,7 +357,7 @@ const dateMergeFn = () => {
// 默认打开页面请求一次所有数据,并保存在数据源
async function init() {
await getPrdList()
// await getList()
await getList()
};
onMounted(() => {
......
<template>
<div ref="chartRef"
:class="className"
:style="{ height: height, width: width }"></div>
</template>
<script setup>
import * as echarts from 'echarts'
import { useWindowResize } from '@/hooks'
const props = defineProps({
className: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '400px'
},
// 图表总体数据对象
chartData: {
type: Object,
required: true,
validator: (val) => {
// 必要属性
// title: 图表标题,data:图表系列数据, xData:图表x轴数据, yName:图表y轴名称
const requiredPrototype = ['title', 'data', 'xData', 'yName']
return requiredPrototype.every(key => {
return Object.prototype.hasOwnProperty.call(val, key)
})
}
},
// 图表是否显示工具
showTool: {
type: Boolean,
default: true
}
})
let chart = null
const chartRef = ref(null)
const myThousand = ref(true)
const setOptions = () => {
chart.setOption({
color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#444648', '#fc8452', '#9a60b4', '#ea7ccc'],
title: {
text: this.chartData.title,
},
tooltip: {
trigger: 'axis'
},
legend: {
data: this.chartData.data?.map(o => o.name)
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
toolbox: {
show: this.showTool,
feature: {
saveAsImage: {}, // 保存为图片
magicType: {
type: ['line', 'bar', 'stack', 'tiled'] // 切换图表类型
},
myThousandTool: {
show: true,
title: '切换万单位',
icon: 'path://M50,50 L100,50 L100,100 L150,100 L150,150 L100,150 L100,200 L50,200 L50,150 L0,150 L0,100 L50,100 Z',
onclick: () => {
this.myThousand = !this.myThousand
this.lockThousand = !this.lockThousand
this.setOptions()
}
}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: this.chartData.xData
},
yAxis: {
type: 'value',
name: (this.myThousand ? '(万)' : '') + this.chartData.yName
},
series: [
...this.chartData.data?.map(o => {
return {
name: o.name,
type: 'line',
data: o.data.map(num => {
return parseFloat((this.myThousand ? num / 10000 : num).toFixed(2))
})
}
})
]
})
}
watch(() => props.chartData, () => {
// 自动计算是否需要过万单位
// if (!this.lockThousand) {
// this.myThousand = this.chartData.data?.some(o => {
// return o.data.some(num => num >= 10000)
// })
// }
chart && setOptions()
})
watch(() => props.showTool, () => {
setOptions()
})
const initChart = () => {
chart = echarts.init(chartRef.value)
setOptions()
}
const resize = () => {
chart.value.resize()
}
useWindowResize(resize)
onMounted(() => {
nextTick(() => {
initChart()
})
})
onBeforeUnmount(() => {
if (!chart) {
return
}
chart.dispose()
chart = null
})
</script>
\ No newline at end of file
<template>
<div class="app-container">
<!-- 套表 Item -->
<div class="excel_charts_item"
v-for="item, index in platformSalesList"
:key="index">
<!-- 查询表单 -->
<el-form :model="item.queryParams"
:inline="true"
label-width="68px"
v-show="item.showSearch">
<el-form-item label="商品标签">
<el-select v-model="item.queryParams.prdTagId"
:disabled="!!item.queryParams.seriesId || !!item.queryParams.prdCode"
placeholder="选择标签"
clearable
filterable
@change="prdTagChange(item)">
<el-option v-for="o in prdTagList"
:key="o.prdTagId"
:label="o.prdTagName"
:value="o.prdTagId">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="系列名称">
<el-select v-model="item.queryParams.seriesId"
:disabled="!!item.queryParams.prdTagId"
placeholder="选择名称"
clearable
filterable
@change="seriesChange(item)">
<el-option v-for="o in allSeriesList"
:key="o.seriesId"
:label="o.seriesName"
:value="o.seriesId">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="商品名称">
<el-select v-model="item.queryParams.prdCode"
:disabled="!!item.queryParams.prdTagId"
placeholder="选择名称"
clearable
filterable
@change="prdChange(item)">
<el-option v-for="o in item.prdList"
:key="o.prdCode"
:label="o.prdName"
:value="o.prdCode">
</el-option>
</el-select>
</el-form-item>
<el-form-item class="del_excel_charts"
v-if="index !== 0">
<el-button type="danger"
icon="el-icon-delete"
@click="platformSalesList.splice(index, 1)">删除表</el-button>
</el-form-item>
</el-form>
<!-- 操作工具 -->
<el-row :gutter="10"
class="mb8">
<right-toolbar v-model:showSearch="item.showSearch"
@queryTable="getSaleList(item)"
:columns="item.columns">
<el-tooltip class="item"
effect="dark"
:content="item.showTable ? '隐藏表格' : '显示表格'"
placement="top">
<el-button circle
icon="View"
@click="item.showTable = !item.showTable" />
</el-tooltip>
<el-tooltip class="item"
effect="dark"
:content="item.showEcharts ? '隐藏图表' : '显示图表'"
placement="top">
<el-button circle
icon="Coin"
@click="item.showEcharts = !item.showEcharts" />
</el-tooltip>
</right-toolbar>
</el-row>
<!-- 单品销售数据 -->
<el-table v-loading="item.loading"
:data="item.saleList"
show-summary
:summary-method="getSummaries"
border
v-show="item.showTable">
<el-table-column label="平台"
align="center"
prop="platformId"
min-width="110px"
sortable
:formatter="formatterPlatForm"></el-table-column>
<!-- <el-table-column label="CNY目标" prop="targetSaleSum" align="center"
:formatter="(row, cell, cellValue) => convertToUnit(cellValue)" min-width="145px" sortable
v-if="item.columns[1].visible"></el-table-column> -->
<el-table-column label="YTD"
align="center"
v-if="item.columns[0].visible">
<!-- <el-table-column label="货需量"
prop="YTD.goodsSupplyDemandCount"
align="center"
min-width="100px"
sortable
:formatter="(row, cell, cellValue) => toThousands(cellValue)"></el-table-column> -->
<el-table-column label="销售量"
prop="YTD.saleNum"
align="center"
min-width="100px"
sortable
:formatter="(row, cell, cellValue) => formatNumberWithUnit(cellValue, '', true, false)"></el-table-column>
<el-table-column label="销售额"
prop="YTD.salePrice"
align="center"
min-width="100px"
sortable
:formatter="(row, cell, cellValue) => formatNumberWithUnit(cellValue, '', true, false)">
</el-table-column>
<!-- <el-table-column label="达成率"
prop="YTD.needNumPercentage"
align="center"
min-width="100px"
sortable
:formatter="(row, cell, cellValue) => cellValue + '%'">
</el-table-column> -->
</el-table-column>
<el-table-column label="MTD"
align="center"
v-if="item.columns[1].visible">
<!-- <el-table-column label="货需量"
prop="YTD.goodsSupplyDemandCount"
align="center"
min-width="100px"
sortable
:formatter="(row, cell, cellValue) => toThousands(cellValue)"></el-table-column> -->
<el-table-column label="销售量"
prop="MTD.saleNum"
align="center"
min-width="100px"
sortable
:formatter="(row, cell, cellValue) => formatNumberWithUnit(cellValue, '', true, false)"></el-table-column>
<el-table-column label="销售额"
prop="MTD.salePrice"
align="center"
:formatter="(row, cell, cellValue) => formatNumberWithUnit(cellValue, '', true, false)"
min-width="100px"
sortable></el-table-column>
<!-- <el-table-column label="达成率"
prop="MTD.needNumPercentage"
align="center"
min-width="100px"
sortable
:formatter="(row, cell, cellValue) => cellValue + '%'">
</el-table-column> -->
</el-table-column>
<el-table-column label="近7日"
align="center"
v-if="item.columns[2].visible">
<el-table-column label="销售量"
prop="week.saleNum"
align="center"
min-width="100px"
sortable
:formatter="(row, cell, cellValue) => formatNumberWithUnit(cellValue, '', true, false)"></el-table-column>
<el-table-column label="销售额"
prop="week.salePrice"
align="center"
:formatter="(row, cell, cellValue) => formatNumberWithUnit(cellValue, '', true, false)"
min-width="100px"
sortable></el-table-column>
</el-table-column>
<el-table-column label="昨日"
align="center"
v-if="item.columns[3].visible">
<el-table-column label="日总销量"
prop="last.saleNum"
align="center"
min-width="120px"
sortable
:formatter="(row, cell, cellValue) => formatNumberWithUnit(cellValue, '', true, false)"></el-table-column>
<el-table-column label="日总额"
prop="last.salePrice"
align="center"
:formatter="(row, cell, cellValue) => formatNumberWithUnit(cellValue, '', true, false)"
min-width="100px"
sortable></el-table-column>
</el-table-column>
</el-table>
<!-- 图表展示 -->
<div class="echarts_wrap"
v-show="item.showEcharts">
<!-- 搜索 -->
<div v-show="item.showSearch">
<el-form inline>
<el-form-item>
<el-radio v-for="obj in item.chartData.typeList"
v-model="item.chartData.title"
:label="obj.name"
@change="dataTypeChangeFn(item, obj)"
v-removeConsoleError>{{ obj.name }}</el-radio>
</el-form-item>
<el-form-item>
<el-date-picker v-model="item.queryParams.date"
style="margin-left: 20px;"
type="daterange"
align="right"
unlink-panels
:clearable="false"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
:shortcuts="pickerOptions"
@change="getSaleList(item)">
</el-date-picker>
</el-form-item>
</el-form>
</div>
<!-- 折线图(动态 class:默认上来加载时给一个比较大的高度 -->
<div v-loading="item.loading"
class="chat_wrap"
:class="{ 'chat_height': item.loading }">
<line-chart v-if="item.chartData?.saleCount.length"
:chartData="item.chartData"
:showTool="item.showSearch"></line-chart>
<no-data v-else-if="!item.loading">暂无数据,图表无法显示,换个查询条件</no-data>
</div>
</div>
</div>
<!-- 新增套表 -->
<div class="more_excel_echarts"
@click="createTableAndECharts">新增套表</div>
<!-- 上传货需弹框 -->
<el-dialog title="上传货需"
:visible.sync="uploadDemandImportVisible">
<el-form :model="uploadDemand"
label-width="150px">
<el-form-item label="年份">
<el-date-picker v-model="uploadDemand.year"
type="year"
placeholder="选择年">
</el-date-picker>
</el-form-item>
<el-form-item label="货需表格">
<el-upload class="upload-demo"
drag
action="#"
:http-request="uploadDemandImportFn">
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip">只能上传 excel 文件</div>
</el-upload>
</el-form-item>
<el-form-item>
<el-button type="primary"
@click="submitUploadDemandFn">提交</el-button>
</el-form-item>
</el-form>
</el-dialog>
<!-- 悬浮球抽屉工具箱 -->
<levitated-sphere>
<el-button type="primary"
@click="createTableAndECharts">新增套表</el-button>
<el-button type="warning"
@click="uploadDemandImportVisible = true">上传货需</el-button>
<el-button type="danger"
@click="reset">重置本页</el-button>
</levitated-sphere>
</div>
</template>
<script setup>
import { getSaleListAPI, uploadDemandImportAPI, getSeriesListAPI, getProductListAPI, getSeriesGoodsTagListAPI, getPrdTagDetailAPI } from '@/api'
import { getObjValueByPath, formatNumberWithUnit, deepClone, parseTime } from '@/utils';
import LineChart from './LineChart.vue';
import gsap from 'gsap'
import { useDatePickerOptions } from '@/hooks'
const { proxy } = getCurrentInstance();
const dict = proxy.useDict("sale_platform");
const allSeriesList = ref([]) // 总系列列表
const prdTagList = ref([]) // 总商品标签列表
const platformSalesList = ref([]) // 平台销售数据列表
const platformSalesObj = { // 平台销售数据对象(一套表图数据对应一个对象)
queryParams: { // 查询参数
prdTagId: undefined,
seriesId: undefined,
prdCode: undefined,
date: []
},
loading: false,
showSearch: true,
showTable: true,
showEcharts: true,
saleList: [], // 销售表格数据
prdList: [], // 商品列表
columns: [
// { key: 0, label: `平台`, visible: true },
// { key: 1, label: `CNY目标`, visible: true },
{ key: 2, label: `YTD`, visible: true },
{ key: 3, label: `MTD`, visible: true },
{ key: 4, label: `近7日`, visible: true },
{ key: 5, label: `日销量和总额`, visible: true }
],
chartData: { // 图表相关数据
saleCount: [], // 销售量源数据
salePrice: [], // 销售额源数据
typeList: [{ // 图表数据类型列表
name: '销售量',
prop: 'saleCount',
yName: '个'
}, {
name: '销售额',
prop: 'salePrice',
yName: '元'
}],
// 图表内部需要的属性
title: '销售量', // 图表标题
yName: '个', // y 轴上名字
data: [], // 图表现在展示用数据
xData: [], // 日期(用于 x 轴)
}
}
const { pickerOptions } = useDatePickerOptions()
const uploadDemandImportVisible = ref(false)
const uploadDemand = ref({ // 上传货需数据对象
year: '',
file: ''
})
/** 获取销售数据列表 */
const getSaleList = async (item) => {
item.loading = true
const res = await getSaleListAPI({
seriesId: item.queryParams.seriesId,
prdCode: item.queryParams.prdCode,
prdCodes: item.queryParams.prdCodes,
dateStart: item.queryParams.date ? parseTime(item.queryParams.date[0], '{y}/{m}/{d}') : '',
dateEnd: item.queryParams.date ? parseTime(item.queryParams.date[1], '{y}/{m}/{d}') : ''
})
// 后台字段和结构与前端不太相符合,处理数据↓
// 数据表格(默认只有 T - 1 数据,无法根据传递的时间改变数据,只有传递不同系列和商品才会改变)
item.saleList = []
res.data.table.forEach((o, index) => {
item.saleList.push({
platformId: o.platformId,
targetSaleSum: o.targetSaleSum,
YTD: {
saleNum: o.saleCountY,
salePrice: o.saleSumY,
goodsSupplyDemandCount: o.goodsSupplyDemandCountY,
// needNumPercentage: (formatNumberWithUnit(o.saleCountY, o.goodsSupplyDemandCountY) * 100).toFixed(2)
needNumPercentage: o.saleCountY
},
MTD: {
saleNum: o.saleCountM,
salePrice: o.saleSumM,
goodsSupplyDemandCount: o.goodsSupplyDemandCountM,
// needNumPercentage: (formatNumberWithUnit(o.saleCountY, o.goodsSupplyDemandCountY) * 100).toFixed(2)
needNumPercentage: o.saleCountY
},
week: {
saleNum: o.saleCountW,
salePrice: o.saleSumW
},
last: {
saleNum: o.saleCountYs,
salePrice: o.saleSumYs
},
})
})
// 图表数据
item.chartData.saleCount = []
item.chartData.salePrice = []
Object.keys(res.data.chart).forEach((key, index) => {
// key 是平台的 id
// 统计销售量
const countObj = {
name: dict.sale_platform.value[key]?.label,
data: res.data.chart[key].map(obj => obj.saleCount),
}
item.chartData.saleCount.push(countObj)
// 统计销售额
const priceObj = {
name: dict.sale_platform.value[key]?.label,
data: res.data.chart[key].map(obj => obj.saleSum),
}
item.chartData.salePrice.push(priceObj)
// 统计日期(只需要执行一次,因为每个系列数据里的日期值都相同)
if (index === 0) {
item.chartData.xData = res.data.chart[key].map(obj => parseTime(obj.date, '{m}-{d}'))
}
})
// 默认先显示销售量数据
item.chartData.data = item.chartData.saleCount
item.loading = false
}
// 商品标签列表
const getSeriesGoodsTagList = async () => {
const { data: { total } } = await getSeriesGoodsTagListAPI()
const { data: { rows } } = await getSeriesGoodsTagListAPI({
pageSize: total
})
prdTagList.value = rows
}
// 获取所有系列列表
const getSeriesList = async () => {
const res = await getSeriesListAPI()
allSeriesList.value = res.data
}
// 平台 id 值换平台中文名字
const getProductList = async (item) => {
const { data: { total } } = await getProductListAPI({ seriesId: item.queryParams.seriesId })
const res = await getProductListAPI({ seriesId: item.queryParams.seriesId, pageSize: total })
item.prdList = res.data.rows
return res.data.rows
}
// 格式化平台名字
const formatterPlatForm = (row, cell, cellValue) => {
return dict.sale_platform.value.find(o => o.value == cellValue)?.label
}
// 商品标签改变时
const getSummaries = (param) => {
const { columns, data } = param;
const sums = [];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计';
return;
}
// 只有数值类型才会进入到合计
const values = data.map(item => {
return parseFloat(getObjValueByPath(item, column.property))
});
if (!values.every(value => isNaN(value))) {
sums[index] = values.reduce((prev, curr) => {
const value = parseFloat(curr);
if (!isNaN(value)) {
return prev + curr;
} else {
return prev;
}
}, 0);
if (column.property.includes('saleNum')) {
// 带 Num 是量(后台字段在上面被我处理成其他字段名字了)
return sums[index] = formatNumberWithUnit(sums[index])
} else if (column.property.includes('salePrice')) {
// 带 Price 是价
return sums[index] = formatNumberWithUnit(sums[index])
} else if (column.property.includes('targetSaleSum')) {
// 合计销售目标
return sums[index] = formatNumberWithUnit(sums[index])
} else if (column.property.includes('goodsSupplyDemandCount')) {
// 货需量
return sums[index] = formatNumberWithUnit(sums[index])
} else if (column.property.includes('needNumPercentage')) {
// 达成率
return sums[index] = (sums[index] / data.length).toFixed(2) + '%'
}
sums[index] = sums[index];
} else {
sums[index] = 'N/A';
}
});
return sums;
}
const prdTagChange = async (item) => {
// 获取标签对应下属的商品 code 集合
const response = await getPrdTagDetailAPI({
prdTagId: item.queryParams.prdTagId
})
const prdCodes = response.data.map(o => o.prdCode)
item.queryParams.prdCodes = prdCodes
// 查询对应销售数据
getSaleList(item)
}
// 系列改变时
const seriesChange = async (item) => {
// 对应商品列表改变
const res = await getProductList(item)
// 商品列表里是否有刚才选择的数据,有就不清空,否则清空刚才选中的选项
if (!res.find(o => o.prdCode === item.queryParams.prdCode)) {
item.queryParams.prdCode = ''
}
// 对应销售数据改变
getSaleList(item)
}
// 商品改变时
const prdChange = async (item) => {
// 对应销售数据改变
getSaleList(item)
}
// 创建查询某个系列/商品的套表
const createTableAndECharts = () => {
let item = reactive(deepClone(platformSalesObj))
// 如果是第一套套表,默认填写日期
if (platformSalesList.value.length === 0) {
item.queryParams.date = [new Date().setDate(1), new Date().setDate((new Date().getDate() - 1))] // 默认查询和显示的是当月 1 号到 T-1 日期
} else {
// 非第一套图表,滚动到底
console.log(document.querySelector('.main-container').scrollHeight)
nextTick(() => {
gsap.to(document.documentElement, {
duration: 1,
scrollTop: document.querySelector('.main-container').scrollHeight
})
})
}
platformSalesList.value.push(item)
return item
}
// 折线图数据类型改变
const dataTypeChangeFn = (item, obj) => {
item.chartData.data = item.chartData[obj.prop]
item.chartData.yName = obj.yName
}
// 重置本页
const reset = () => {
platformSalesList.value = []
init()
}
// 上传货需文件选择
const uploadDemandImportFn = (obj) => {
uploadDemand.file = obj.file
}
// 确认上传货需
const submitUploadDemandFn = async () => {
const fd = new FormData()
fd.append('file', uploadDemand.file)
fd.append('year', uploadDemand?.year.getFullYear())
const res = await uploadDemandImportAPI(fd)
$message.success(res.msg)
uploadDemandImportVisible.value = false
}
const init = async () => {
// 默认创建第一个表格
const item = createTableAndECharts()
// 获取销售数据列表
await getSaleList(item);
// 获取商品标签集合列表
await getSeriesGoodsTagList()
// 获取所有系列列表
await getSeriesList()
// 获取所有商品列表
await getProductList(item)
}
init()
</script>
<style scoped
lang="scss">
.app-container {
/* 套表 */
.excel_charts_item {
background-color: var(--el-bg-color-overlay);
padding: 20px;
margin-top: 20px;
/* 图容器 */
.echarts_wrap {
margin-top: 20px;
.chat_wrap {
margin-top: 20px;
}
.chat_height {
height: 400px;
}
}
/* 删除套表 */
.del_excel_charts {
float: right;
margin-top: -5px;
margin-right: 0;
}
/* 第一个套表无需上外边距 */
&:nth-child(1) {
margin-top: 0;
}
.el-form-item {
margin-bottom: 0;
}
}
/* 新增套表 */
.more_excel_echarts {
padding: 10px 15px;
text-align: center;
font-size: 16px;
background-color: var(--el-gray-3);
margin-top: 10px;
cursor: context-menu;
}
}
/* 组件定制样式 */
::v-deep(.el-table) {
/* 表格高度缩小一点 */
.el-table__cell {
padding: 5px 0 !important;
}
/* 表格头部标题换行显示 */
.cell {
white-space: pre-line;
}
}
/* 合计行在滚动条下面解决 */
::v-deep(.el-table) {
overflow: auto;
.el-table__body-wrapper,
.el-table__header-wrapper,
.el-table__footer-wrapper {
overflow: visible;
}
.el-table::after {
position: relative !important;
}
}
/* 网页刚加载时表格最右侧的竖线去掉 */
.el-table--group::after,
.el-table--border::after {
width: 0 !important;
}
/* 表格左上角标题 */
::v-deep(.name_title) {
.cell {
font-size: 18px;
color: rgba(255, 0, 0, 0.437);
}
}
</style>
\ No newline at end of file
......@@ -27,7 +27,7 @@ export default defineConfig(({ mode, command }) => {
rewrite: (p) => p.replace(/^\/dev-api/, '')
},
'/qllan': {
target: 'http://192.168.140.189:8080',
target: 'http://192.168.140.31:8080',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/qllan/, '')
},
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论