✨ feat: 首页增加销售看板
This commit is contained in:
@@ -44,7 +44,7 @@
|
||||
<el-form-item>
|
||||
<el-radio v-model='radioValue' :label="7">
|
||||
指定
|
||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
|
||||
<el-select filterable clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
|
||||
<el-option v-for="item in 31" :key="item" :value="item">{{item}}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<el-form-item>
|
||||
<el-radio v-model='radioValue' :label="4">
|
||||
指定
|
||||
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
|
||||
<el-select filterable clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
|
||||
<el-option v-for="item in 24" :key="item" :value="item-1">{{item-1}}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
|
||||
@@ -1,7 +1,50 @@
|
||||
<template>
|
||||
<div class="all-applications-container">
|
||||
<h3 class="title">全部应用</h3>
|
||||
<el-tabs v-model="activeTabName" class="app-tabs">
|
||||
<!-- 常用应用区域 -->
|
||||
<div class="frequently-used-section">
|
||||
<div class="frequently-used-header">
|
||||
<h3 class="frequently-title">常用应用</h3>
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
class="edit-btn"
|
||||
@click="isEditingFavorites = !isEditingFavorites"
|
||||
>
|
||||
{{ isEditingFavorites ? '完成' : '编辑' }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="frequently-used-grid" v-if="frequentlyUsedApps.length > 0">
|
||||
<div
|
||||
v-for="app in frequentlyUsedApps"
|
||||
:key="`fav-${app.parentPath}-${app.childPath}`"
|
||||
class="frequently-app-item"
|
||||
@click="handleAppClick(getParentMenu(app.parentPath), getChildMenu(app.parentPath, app.childPath))"
|
||||
>
|
||||
<div class="app-icon-wrapper">
|
||||
<svg-icon :icon-class="getChildMenu(app.parentPath, app.childPath).meta.icon || 'documentation'" class="app-icon" />
|
||||
</div>
|
||||
<span class="app-name">{{ getChildMenu(app.parentPath, app.childPath).meta.title }}</span>
|
||||
|
||||
<!-- 删除按钮 - 仅在编辑模式显示 -->
|
||||
<div
|
||||
class="remove-btn"
|
||||
v-if="isEditingFavorites"
|
||||
@click.stop="removeFromFavorites(app.parentPath, app.childPath)"
|
||||
>
|
||||
<i class="el-icon-close"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<el-empty description="暂无常用应用" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 原有全部应用区域 -->
|
||||
<h3 class="title" v-if="isEditingFavorites">全部应用</h3>
|
||||
<el-tabs v-model="activeTabName" class="app-tabs" v-if="isEditingFavorites">
|
||||
<el-tab-pane
|
||||
v-for="menu in filteredMenus"
|
||||
:key="menu.path"
|
||||
@@ -13,10 +56,27 @@
|
||||
v-for="child in menu.children"
|
||||
:key="child.path"
|
||||
class="app-item"
|
||||
@click="handleAppClick(menu, child)"
|
||||
>
|
||||
<!-- @click="handleAppClick(menu, child)" -->
|
||||
<div class="app-icon-wrapper">
|
||||
<svg-icon :icon-class="child.meta.icon || 'documentation'" class="app-icon" />
|
||||
|
||||
<!-- 添加到常用按钮 -->
|
||||
<div
|
||||
class="add-to-favorite-btn"
|
||||
@click.stop="addToFavorites(menu.path, child.path)"
|
||||
v-if="!isInFavorites(menu.path, child.path)"
|
||||
>
|
||||
<i class="el-icon-star-off"></i>
|
||||
</div>
|
||||
|
||||
<!-- 已在常用中的标识 -->
|
||||
<div
|
||||
class="in-favorite-indicator"
|
||||
v-if="isInFavorites(menu.path, child.path)"
|
||||
>
|
||||
<i class="el-icon-star-on"></i>
|
||||
</div>
|
||||
</div>
|
||||
<span class="app-name">{{ child.meta.title }}</span>
|
||||
</div>
|
||||
@@ -35,14 +95,16 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
allMenus: [],
|
||||
activeTabName: ''
|
||||
activeTabName: '',
|
||||
frequentlyUsedApps: [], // 存储常用应用
|
||||
isEditingFavorites: false // 是否处于编辑常用应用模式
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredMenus() {
|
||||
const filterHidden = (menus) => {
|
||||
return menus
|
||||
.filter(menu => menu.hidden !== true)
|
||||
.filter(menu => menu.hidden!== true)
|
||||
.map(menu => {
|
||||
if (menu.children) {
|
||||
menu.children = filterHidden(menu.children)
|
||||
@@ -58,6 +120,7 @@ export default {
|
||||
},
|
||||
created() {
|
||||
this.fetchMenus()
|
||||
this.loadFrequentlyUsedApps()
|
||||
},
|
||||
methods: {
|
||||
fetchMenus() {
|
||||
@@ -68,7 +131,72 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 加载常用应用(从localStorage)
|
||||
loadFrequentlyUsedApps() {
|
||||
const saved = localStorage.getItem('frequentlyUsedApps')
|
||||
if (saved) {
|
||||
try {
|
||||
this.frequentlyUsedApps = JSON.parse(saved)
|
||||
} catch (e) {
|
||||
console.error('Failed to parse frequently used apps', e)
|
||||
this.frequentlyUsedApps = []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 保存常用应用到localStorage
|
||||
saveFrequentlyUsedApps() {
|
||||
localStorage.setItem('frequentlyUsedApps', JSON.stringify(this.frequentlyUsedApps))
|
||||
},
|
||||
|
||||
// 添加到常用应用
|
||||
addToFavorites(parentPath, childPath) {
|
||||
if (!this.isInFavorites(parentPath, childPath)) {
|
||||
// 限制最多10个常用应用
|
||||
if (this.frequentlyUsedApps.length >= 10) {
|
||||
this.$message.warning('常用应用最多只能添加10个')
|
||||
return
|
||||
}
|
||||
|
||||
this.frequentlyUsedApps.unshift({ parentPath, childPath })
|
||||
this.saveFrequentlyUsedApps()
|
||||
this.$message.success('已添加到常用应用')
|
||||
}
|
||||
},
|
||||
|
||||
// 从常用应用中移除
|
||||
removeFromFavorites(parentPath, childPath) {
|
||||
this.frequentlyUsedApps = this.frequentlyUsedApps.filter(
|
||||
app =>!(app.parentPath === parentPath && app.childPath === childPath)
|
||||
)
|
||||
this.saveFrequentlyUsedApps()
|
||||
},
|
||||
|
||||
// 检查应用是否在常用列表中
|
||||
isInFavorites(parentPath, childPath) {
|
||||
return this.frequentlyUsedApps.some(
|
||||
app => app.parentPath === parentPath && app.childPath === childPath
|
||||
)
|
||||
},
|
||||
|
||||
// 根据路径获取父菜单
|
||||
getParentMenu(parentPath) {
|
||||
return this.filteredMenus.find(menu => menu.path === parentPath)
|
||||
},
|
||||
|
||||
// 根据路径获取子菜单
|
||||
getChildMenu(parentPath, childPath) {
|
||||
const parent = this.getParentMenu(parentPath)
|
||||
if (parent && parent.children) {
|
||||
return parent.children.find(child => child.path === childPath)
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
handleAppClick(parentMenu, childMenu) {
|
||||
if (!childMenu) return
|
||||
|
||||
const basePath = parentMenu.path
|
||||
const fullPath = path.resolve(basePath, childMenu.path)
|
||||
this.$router.push(fullPath)
|
||||
@@ -80,23 +208,73 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
.all-applications-container {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-top: 20px;
|
||||
padding: 16px; /* 调整间距,更贴近飞书紧凑感 */
|
||||
border-radius: 12px; /* 飞书常用较大圆角 */
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
// 常用应用样式
|
||||
.frequently-used-section {
|
||||
margin-bottom: 32px; /* 增大间距 */
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #ebeef5; /* 飞书浅灰边框色 */
|
||||
}
|
||||
|
||||
.frequently-used-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
// margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.frequently-title {
|
||||
font-size: 16px; /* 稍小字体,飞书风格更简洁 */
|
||||
font-weight: 500; /* 调整 FontWeight */
|
||||
color: #1f2329; /* 深一点的标题色 */
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
color: #0078ff; /* 飞书常用的主题蓝 */
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.frequently-used-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(112px, 1fr)); /* 调整卡片宽度 */
|
||||
gap: 20px; /* 增大间距 */
|
||||
}
|
||||
|
||||
.frequently-app-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 16px; /* 调整内边距 */
|
||||
border-radius: 12px; /* 大圆角 */
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
background-color: #f7f8fa; /* 飞书卡片常用浅灰底色 */
|
||||
|
||||
&:hover {
|
||||
background-color: #eaeef7; /* hover 时的浅灰 */
|
||||
}
|
||||
}
|
||||
|
||||
// 全部应用标题
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 20px;
|
||||
color: #1f2329;
|
||||
}
|
||||
|
||||
// 应用网格布局
|
||||
.app-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
gap: 20px;
|
||||
padding-top: 10px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); /* 调整卡片宽度 */
|
||||
gap: 24px; /* 增大间距 */
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.app-item {
|
||||
@@ -104,37 +282,110 @@ export default {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
transition: background-color 0.3s ease;
|
||||
position: relative;
|
||||
background-color: #f7f8fa;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
background-color: #eaeef7;
|
||||
}
|
||||
}
|
||||
|
||||
// 应用图标样式
|
||||
.app-icon-wrapper {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 12px;
|
||||
width: 48px; /* 稍小图标容器 */
|
||||
height: 48px;
|
||||
border-radius: 10px; /* 图标容器圆角 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 10px;
|
||||
background-color: #f0f5ff;
|
||||
margin-bottom: 12px;
|
||||
background-color: #e6f0ff; /* 飞书风格的浅蓝底色 */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
font-size: 28px;
|
||||
color: #409eff;
|
||||
font-size: 24px; /* 调整图标大小 */
|
||||
color: #0078ff; /* 主题蓝 */
|
||||
}
|
||||
|
||||
// 应用名称样式
|
||||
.app-name {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
color: #515767; /* 飞书常用的文本色 */
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// 添加到常用按钮
|
||||
.add-to-favorite-btn {
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
right: 4px;
|
||||
width: 24px; /* 稍大按钮 */
|
||||
height: 24px;
|
||||
border-radius: 6px; /* 小圆角 */
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: #c0c4cc;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: #ffb400; /* 飞书常用的强调色 */
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
// 已在常用中的标识
|
||||
.in-favorite-indicator {
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
right: 4px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 6px;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
color: #ffb400;
|
||||
}
|
||||
|
||||
// 删除常用应用按钮
|
||||
.remove-btn {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: -6px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 6px;
|
||||
background-color: #ff4d4f; /* 飞书删除按钮红 */
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: #ff3839;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
// 标签页样式
|
||||
::v-deep .el-tabs__header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -142,8 +393,4 @@ export default {
|
||||
::v-deep .el-tabs__nav-wrap::after {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
::v-deep .el-tabs__item {
|
||||
font-size: 15px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
170
klp-ui/src/views/components/OrderDashboard.vue
Normal file
170
klp-ui/src/views/components/OrderDashboard.vue
Normal file
@@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<div
|
||||
class="order-analysis-dashboard"
|
||||
v-loading="loading"
|
||||
>
|
||||
<!-- 业绩区 -->
|
||||
<el-tabs v-model="activeTab" type="card">
|
||||
<el-tab-pane label="业绩总览" name="performance">
|
||||
<PerformanceArea mode="mini" :performance-area="dashboardData.performanceArea" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="订单统计" name="currentSituation">
|
||||
<CurrentSituationArea mode="mini" :current-situation-area="dashboardData.currentSituationArea" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="采购推荐" name="recommendation">
|
||||
<RecommendationArea mode="mini" :recommendation-area="dashboardData.recommendationArea" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PerformanceArea from '@/views/wms/order/components/PerformanceArea.vue'
|
||||
import CurrentSituationArea from '@/views/wms/order/components/CurrentSituationArea.vue'
|
||||
import RecommendationArea from '@/views/wms/order/components/RecommendationArea.vue'
|
||||
import { getDashboardData } from '@/api/wms/order'
|
||||
|
||||
export default {
|
||||
name: 'OrderAnalysisDashboard',
|
||||
components: {
|
||||
PerformanceArea,
|
||||
CurrentSituationArea,
|
||||
RecommendationArea,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 新的数据结构
|
||||
dashboardData: {
|
||||
performanceArea: {},
|
||||
currentSituationArea: {},
|
||||
recommendationArea: {}
|
||||
},
|
||||
// 新增定时刷新相关数据
|
||||
drawerVisible: false,
|
||||
autoRefresh: false,
|
||||
refreshInterval: 30, // 默认30秒
|
||||
refreshTimer: null,
|
||||
loading: false,
|
||||
activeTab: 'performance'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchAllData()
|
||||
this.loadRefreshSetting()
|
||||
this.startAutoRefresh()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearAutoRefresh()
|
||||
},
|
||||
methods: {
|
||||
async fetchAllData() {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await getDashboardData()
|
||||
const data = res
|
||||
|
||||
// 更新新的数据结构
|
||||
this.dashboardData = {
|
||||
performanceArea: data.performanceArea || {},
|
||||
currentSituationArea: data.currentSituationArea || {},
|
||||
recommendationArea: data.recommendationArea || {}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取数据看板数据失败:', error)
|
||||
this.$message.error('获取数据失败,请稍后重试')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
handleRefresh() {
|
||||
this.fetchAllData()
|
||||
},
|
||||
// 定时刷新相关
|
||||
startAutoRefresh() {
|
||||
this.clearAutoRefresh()
|
||||
if (this.autoRefresh) {
|
||||
this.refreshTimer = setInterval(() => {
|
||||
this.fetchAllData()
|
||||
}, this.refreshInterval * 1000)
|
||||
}
|
||||
},
|
||||
clearAutoRefresh() {
|
||||
if (this.refreshTimer) {
|
||||
clearInterval(this.refreshTimer)
|
||||
this.refreshTimer = null
|
||||
}
|
||||
},
|
||||
saveRefreshSetting() {
|
||||
// 可持久化到localStorage
|
||||
localStorage.setItem('orderDashboardAutoRefresh', JSON.stringify({
|
||||
autoRefresh: this.autoRefresh,
|
||||
refreshInterval: this.refreshInterval
|
||||
}))
|
||||
this.drawerVisible = false
|
||||
this.startAutoRefresh()
|
||||
},
|
||||
loadRefreshSetting() {
|
||||
const setting = localStorage.getItem('orderDashboardAutoRefresh')
|
||||
if (setting) {
|
||||
const { autoRefresh, refreshInterval } = JSON.parse(setting)
|
||||
this.autoRefresh = autoRefresh
|
||||
this.refreshInterval = refreshInterval
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
autoRefresh(val) {
|
||||
if (!val) {
|
||||
this.clearAutoRefresh()
|
||||
}
|
||||
},
|
||||
refreshInterval(val) {
|
||||
if (this.autoRefresh) {
|
||||
this.startAutoRefresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.order-analysis-dashboard {
|
||||
padding: 24px;
|
||||
background-color: #f7f8fa;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.section-row {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin-bottom: 20px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.section-title h2 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.section-title p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.top-row,
|
||||
.chart-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.chart-row > .el-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<template>
|
||||
<div class="dashboard-root">
|
||||
<!-- 第一行:头像+欢迎语 -->
|
||||
<!-- 第一行:头像+欢迎语 -->
|
||||
<div class="user-greeting-row">
|
||||
<img :src="avatar" class="user-avatar" alt="头像" />
|
||||
<div class="greeting-text">
|
||||
@@ -12,7 +12,20 @@
|
||||
</div>
|
||||
|
||||
<!-- 全部应用 -->
|
||||
<AllApplications />
|
||||
<el-row>
|
||||
<AllApplications />
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
办公管理数据
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<OrderDashboard />
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 数据概览区 -->
|
||||
<!-- <div class="data-overview">
|
||||
<div v-for="(card, index) in dataCards" :key="index"
|
||||
@@ -46,10 +59,12 @@
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
import AllApplications from '@/views/components/AllApplications.vue';
|
||||
import OrderDashboard from '@/views/components/OrderDashboard.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AllApplications
|
||||
AllApplications,
|
||||
OrderDashboard
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -346,50 +361,71 @@ export default {
|
||||
background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.data-overview {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.data-card {
|
||||
padding: 24px;
|
||||
border-radius: 16px;
|
||||
backdrop-filter: blur(4px);
|
||||
background: rgba(255,255,255,0.8);
|
||||
box-shadow: 0 4px 24px 0 rgba(0,0,0,0.06);
|
||||
border: 1px solid rgba(255,255,255,0.2);
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
box-shadow: 0 4px 24px 0 rgba(0, 0, 0, 0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.data-card:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.data-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.data-card-title {
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.data-card-value {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.data-card-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
.icon-blue { color: #3b82f6; }
|
||||
.icon-green { color: #22c55e; }
|
||||
.icon-yellow { color: #eab308; }
|
||||
.icon-purple { color: #a855f7; }
|
||||
|
||||
.icon-blue {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.icon-green {
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.icon-yellow {
|
||||
color: #eab308;
|
||||
}
|
||||
|
||||
.icon-purple {
|
||||
color: #a855f7;
|
||||
}
|
||||
|
||||
.data-card-chart {
|
||||
height: 48px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chart-inner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -401,20 +437,23 @@ export default {
|
||||
gap: 24px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.business-module {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05);
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.business-module:hover {
|
||||
box-shadow: 0 4px 16px 0 rgba(0,0,0,0.1);
|
||||
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.business-module-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
@@ -425,17 +464,40 @@ export default {
|
||||
flex-shrink: 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.business-module-icon i {
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
}
|
||||
.bg-blue { background: #3b82f6; }
|
||||
.bg-green { background: #22c55e; }
|
||||
.bg-yellow { background: #eab308; }
|
||||
.bg-purple { background: #a855f7; }
|
||||
.bg-red { background: #ef4444; }
|
||||
.bg-indigo { background: #6366f1; }
|
||||
.bg-teal { background: #14b8a6; }
|
||||
|
||||
.bg-blue {
|
||||
background: #3b82f6;
|
||||
}
|
||||
|
||||
.bg-green {
|
||||
background: #22c55e;
|
||||
}
|
||||
|
||||
.bg-yellow {
|
||||
background: #eab308;
|
||||
}
|
||||
|
||||
.bg-purple {
|
||||
background: #a855f7;
|
||||
}
|
||||
|
||||
.bg-red {
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
.bg-indigo {
|
||||
background: #6366f1;
|
||||
}
|
||||
|
||||
.bg-teal {
|
||||
background: #14b8a6;
|
||||
}
|
||||
|
||||
.business-module-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
@@ -447,30 +509,37 @@ export default {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 24px;
|
||||
}
|
||||
.monitor-resource, .monitor-records {
|
||||
|
||||
.monitor-resource,
|
||||
.monitor-records {
|
||||
padding: 24px;
|
||||
border-radius: 16px;
|
||||
background: #fff;
|
||||
box-shadow: 0 4px 24px 0 rgba(0,0,0,0.06);
|
||||
box-shadow: 0 4px 24px 0 rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.monitor-title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.monitor-resource-charts {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.monitor-resource-chart {
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.monitor-records-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.monitor-record-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -478,9 +547,11 @@ export default {
|
||||
border-radius: 12px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.monitor-record-item:hover {
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.monitor-record-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
@@ -490,24 +561,29 @@ export default {
|
||||
justify-content: center;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.monitor-record-icon-inner {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.monitor-record-action {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.monitor-record-time {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.user-greeting-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
@@ -516,16 +592,19 @@ export default {
|
||||
border: 2px solid #e0e0e0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.greeting-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.greeting-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.greeting-desc {
|
||||
font-size: 16px;
|
||||
color: #888;
|
||||
@@ -539,4 +618,3 @@ export default {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="current-situation-area">
|
||||
<el-row :gutter="20">
|
||||
<el-row :gutter="20" v-if="mode === 'normal'">
|
||||
<!-- 订单所需的产品统计 -->
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="situation-card">
|
||||
@@ -8,7 +8,8 @@
|
||||
<span>订单所需的产品统计</span>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<el-table :data="orderProductStatistics" size="small" height="320" v-loading="!orderProductStatistics.length">
|
||||
<el-table :data="orderProductStatistics" size="small" height="320"
|
||||
v-loading="!orderProductStatistics.length">
|
||||
<el-table-column prop="productName" label="产品名称" width="120" />
|
||||
<el-table-column prop="orderDemandQuantity" label="需求数量" width="80" />
|
||||
<el-table-column prop="currentStockQuantity" label="库存数量" width="80" />
|
||||
@@ -28,7 +29,7 @@
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
|
||||
<!-- 根据BOM计算的原料需求 -->
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="situation-card">
|
||||
@@ -36,7 +37,8 @@
|
||||
<span>BOM原料需求</span>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<el-table :data="productMaterialRequirements" size="small" height="320" v-loading="!productMaterialRequirements.length">
|
||||
<el-table :data="productMaterialRequirements" size="small" height="320"
|
||||
v-loading="!productMaterialRequirements.length">
|
||||
<el-table-column prop="productName" label="产品" width="100" />
|
||||
<el-table-column prop="materialName" label="原料" width="100" />
|
||||
<el-table-column prop="requiredQuantity" label="需求数量" width="80" />
|
||||
@@ -57,7 +59,7 @@
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
|
||||
<!-- 原料库存和需求情况 -->
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="situation-card">
|
||||
@@ -93,6 +95,33 @@
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="current-situation-area-mini" v-else>
|
||||
<el-card shadow="hover" class="situation-card">
|
||||
<div slot="header" class="card-header">
|
||||
<span>订单所需的产品统计</span>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<el-table :data="orderProductStatistics" size="small" height="320" v-loading="!orderProductStatistics.length">
|
||||
<el-table-column prop="productName" label="产品名称" width="120" />
|
||||
<el-table-column prop="orderDemandQuantity" label="需求数量" width="80" />
|
||||
<el-table-column prop="currentStockQuantity" label="库存数量" width="80" />
|
||||
<el-table-column prop="stockGap" label="库存缺口" width="80">
|
||||
<template slot-scope="scope">
|
||||
<span :class="getStockGapClass(scope.row.stockGap)">
|
||||
{{ scope.row.stockGap }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="relatedOrderCount" label="相关订单" width="80" />
|
||||
</el-table>
|
||||
<div v-if="!orderProductStatistics.length" class="empty-data">
|
||||
<i class="el-icon-warning-outline"></i>
|
||||
<p>暂无数据</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -104,6 +133,10 @@ export default {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({})
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'normal'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -195,4 +228,4 @@ export default {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="performance-area">
|
||||
<el-row :gutter="20">
|
||||
<el-row :gutter="20" v-if="mode === 'normal'">
|
||||
<!-- 产品销售情况 -->
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="performance-card">
|
||||
@@ -56,6 +56,45 @@
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="performance-area-mini" v-else>
|
||||
<!-- <el-tabs tab-position="left"> -->
|
||||
<!-- <el-tab-pane label="产品销售情况" name="productSales">
|
||||
<div class="chart-container" ref="productSalesChart" style="width: 100%;"></div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="销售人员业绩" name="salesPerson">
|
||||
<div class="chart-container" ref="salesPersonChart" style="width: 100%;"></div>
|
||||
</el-tab-pane> -->
|
||||
<!-- <el-tab-pane label="总订单数量统计" name="orderCount"> -->
|
||||
<div class="stats-container">
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">{{ orderCountStatistics.totalOrderCount || 0 }}</div>
|
||||
<div class="stat-label">总订单数</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">{{ orderCountStatistics.completedOrderCount || 0 }}</div>
|
||||
<div class="stat-label">已完成</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">{{ orderCountStatistics.inProgressOrderCount || 0 }}</div>
|
||||
<div class="stat-label">进行中</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">{{ orderCountStatistics.pendingOrderCount || 0 }}</div>
|
||||
<div class="stat-label">待处理</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">{{ orderCountStatistics.monthlyNewOrderCount || 0 }}</div>
|
||||
<div class="stat-label">本月新增</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-number">{{ ((orderCountStatistics.completionRate || 0) * 100).toFixed(1) }}%</div>
|
||||
<div class="stat-label">完成率</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- </el-tab-pane> -->
|
||||
<!-- </el-tabs> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -69,6 +108,10 @@ export default {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({})
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'normal'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="recommendation-area">
|
||||
<el-row :gutter="20">
|
||||
<el-row :gutter="20" v-if="mode === 'normal'">
|
||||
<!-- 订单维度推荐 -->
|
||||
<el-col :span="12">
|
||||
<el-card shadow="hover" class="recommendation-card">
|
||||
@@ -61,6 +61,32 @@
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="recommendation-area-mini" v-else>
|
||||
<el-card shadow="hover" class="recommendation-card">
|
||||
<div class="table-container">
|
||||
<el-table :data="materialRecommendations" size="small" height="320" v-loading="!materialRecommendations.length">
|
||||
<el-table-column prop="materialName" label="原料名称" width="120" />
|
||||
<el-table-column prop="recommendedPurchaseQuantity" label="推荐采购数量" width="120" />
|
||||
<el-table-column prop="recommendedSupplier" label="推荐供应商" width="100" show-overflow-tooltip />
|
||||
<el-table-column prop="urgencyLevel" label="紧急程度" width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="getUrgencyType(scope.row.urgencyLevel)" size="mini">
|
||||
{{ scope.row.urgencyLevel }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="recommendationReason" label="推荐原因" width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="suggestedAction" label="建议操作" width="100" />
|
||||
<el-table-column prop="estimatedArrivalTime" label="预计到货时间" width="120" />
|
||||
</el-table>
|
||||
<div v-if="!materialRecommendations.length" class="empty-data">
|
||||
<i class="el-icon-warning-outline"></i>
|
||||
<p>暂无推荐数据</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -72,6 +98,10 @@ export default {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({})
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'normal'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div style="max-height:60vh;overflow:auto;padding-right:8px;">
|
||||
<el-form :model="detailForm" :rules="detailRules" ref="detailForm" label-width="80px" style="overflow:visible;">
|
||||
<el-form-item label="产线" prop="lineId">
|
||||
<el-select v-model="detailForm.lineId" placeholder="请选择产线" filterable @change="onLineChange">
|
||||
<el-select clearable v-model="detailForm.lineId" placeholder="请选择产线" filterable @change="onLineChange">
|
||||
<el-option v-for="item in productionLineList" :key="item.lineId" :label="item.lineName"
|
||||
:value="item.lineId" />
|
||||
</el-select>
|
||||
@@ -14,7 +14,7 @@
|
||||
<el-option v-for="item in productList" :key="item.productId" :label="item.productName"
|
||||
:value="item.productId" />
|
||||
</el-select> -->
|
||||
<el-select v-model="detailForm.batchId" placeholder="请选择批次" filterable @change="onBatchChange">
|
||||
<el-select clearable v-model="detailForm.batchId" placeholder="请选择批次" filterable @change="onBatchChange">
|
||||
<el-option v-for="item in batchList" :key="item.batchId" :label="item.batchNo" :value="item.batchId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
@@ -70,7 +70,7 @@ export default {
|
||||
remark: ''
|
||||
},
|
||||
detailRules: {
|
||||
productId: [{ required: true, message: '请选择产品', trigger: 'change' }],
|
||||
batchId: [{ required: true, message: '请选择产品', trigger: 'change' }],
|
||||
lineId: [{ required: true, message: '请选择产线', trigger: 'change' }],
|
||||
quantity: [{ required: true, message: '请输入排产数量', trigger: 'blur' }],
|
||||
dateRange: [{ required: true, type: 'array', len: 2, message: '请选择计划日期区间', trigger: 'change' }]
|
||||
|
||||
Reference in New Issue
Block a user