Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
B
baifang-java
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
单欣鑫
baifang-java
Commits
6a3f72ad
Commit
6a3f72ad
authored
Jun 06, 2025
by
赵乐
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
首页
parent
8628b9ca
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
1895 additions
and
401 deletions
+1895
-401
index.ts
yudao-ui/yudao-ui-admin-vue3/src/api/visit/home/index.ts
+41
-0
Index copy.vue
yudao-ui/yudao-ui-admin-vue3/src/views/Home/Index copy.vue
+422
-0
Index.vue
yudao-ui/yudao-ui-admin-vue3/src/views/Home/Index.vue
+375
-401
YearRangeSelector.vue
.../yudao-ui-admin-vue3/src/views/Home/YearRangeSelector.vue
+316
-0
card.vue
yudao-ui/yudao-ui-admin-vue3/src/views/Home/card.vue
+102
-0
BarChart.vue
...udao-ui-admin-vue3/src/views/Home/components/BarChart.vue
+137
-0
LineChart.vue
...dao-ui-admin-vue3/src/views/Home/components/LineChart.vue
+154
-0
PieChart.vue
...udao-ui-admin-vue3/src/views/Home/components/PieChart.vue
+147
-0
barChart_1.vue
...ao-ui-admin-vue3/src/views/Home/components/barChart_1.vue
+128
-0
chartsCard.vue
...ao-ui-admin-vue3/src/views/Home/components/chartsCard.vue
+73
-0
No files found.
yudao-ui/yudao-ui-admin-vue3/src/api/visit/home/index.ts
0 → 100644
View file @
6a3f72ad
import
request
from
'
@/config/axios
'
// 客户信息 API
export
const
homesApi
=
{
// 查询卡片信息
getHomeInfoFirst
:
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
`/visit/home/getHomeInfoFirst`
,
params
})
},
// 竖柱状图数据
getHomeInfoBfztj
:
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
`/visit/home/getHomeInfoBfztj`
,
params
})
},
// 折线图数据
getHomeInfoBfrtj
:
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
`/visit/home/getHomeInfoBfrtj`
,
params
})
},
// 客户性质等级占比情况
getHomeInfoKhxzdjzbqk
:
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
`/visit/home/getHomeInfoKhxzdjzbqk`
,
params
})
},
// 拜访人均分布情况
getHomeInfoBfrjfbqk
:
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
`/visit/home/getHomeInfoBfrjfbqk`
,
params
})
},
// 客户拜访方式占比情况
getHomeInfoKhbffszbqk
:
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
`/visit/home/getHomeInfoKhbffszbqk`
,
params
})
},
// 客户部门占比情况
getHomeInfoKhbmzbqk
:
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
`/visit/home/getHomeInfoKhbmzbqk`
,
params
})
},
// 客户拜访类型占比情况
getHomeInfoKhbflxzbqk
:
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
`/visit/home/getHomeInfoKhbflxzbqk`
,
params
})
},
// 拜访产品类型占比情况
getHomeInfoBfcplxzbqk
:
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
`/visit/home/getHomeInfoBfcplxzbqk`
,
params
})
},
}
yudao-ui/yudao-ui-admin-vue3/src/views/Home/Index copy.vue
0 → 100644
View file @
6a3f72ad
<
template
>
<div>
<el-card
shadow=
"never"
>
<el-skeleton
:loading=
"loading"
animated
>
<el-row
:gutter=
"16"
justify=
"space-between"
>
<el-col
:xl=
"12"
:lg=
"12"
:md=
"12"
:sm=
"24"
:xs=
"24"
>
<div
class=
"flex items-center"
>
<el-avatar
:src=
"avatar"
:size=
"70"
class=
"mr-16px"
>
<img
src=
"@/assets/imgs/logo.png"
alt=
""
/>
</el-avatar>
<div>
<div
class=
"text-20px"
>
{{
t
(
'
workplace.welcome
'
)
}}
{{
username
}}
{{
t
(
'
workplace.happyDay
'
)
}}
</div>
<div
class=
"mt-10px text-14px text-gray-500"
>
{{
t
(
'
workplace.toady
'
)
}}
,20℃ - 32℃!
</div>
</div>
</div>
</el-col>
<el-col
:xl=
"12"
:lg=
"12"
:md=
"12"
:sm=
"24"
:xs=
"24"
>
<div
class=
"h-70px flex items-center justify-end lt-sm:mt-10px"
>
<div
class=
"px-8px text-right"
>
<div
class=
"mb-16px text-14px text-gray-400"
>
{{
t
(
'
workplace.project
'
)
}}
</div>
<CountTo
class=
"text-20px"
:start-val=
"0"
:end-val=
"totalSate.project"
:duration=
"2600"
/>
</div>
<el-divider
direction=
"vertical"
/>
<div
class=
"px-8px text-right"
>
<div
class=
"mb-16px text-14px text-gray-400"
>
{{
t
(
'
workplace.toDo
'
)
}}
</div>
<CountTo
class=
"text-20px"
:start-val=
"0"
:end-val=
"totalSate.todo"
:duration=
"2600"
/>
</div>
<el-divider
direction=
"vertical"
border-style=
"dashed"
/>
<div
class=
"px-8px text-right"
>
<div
class=
"mb-16px text-14px text-gray-400"
>
{{
t
(
'
workplace.access
'
)
}}
</div>
<CountTo
class=
"text-20px"
:start-val=
"0"
:end-val=
"totalSate.access"
:duration=
"2600"
/>
</div>
</div>
</el-col>
</el-row>
</el-skeleton>
</el-card>
</div>
<el-row
class=
"mt-8px"
:gutter=
"8"
justify=
"space-between"
>
<el-col
:xl=
"16"
:lg=
"16"
:md=
"24"
:sm=
"24"
:xs=
"24"
class=
"mb-8px"
>
<el-card
shadow=
"never"
>
<template
#header
>
<div
class=
"h-3 flex justify-between"
>
<span>
{{
t
(
'
workplace.project
'
)
}}
</span>
<el-link
type=
"primary"
:underline=
"false"
href=
"https://github.com/yudaocode"
target=
"_blank"
>
{{
t
(
'
action.more
'
)
}}
</el-link>
</div>
</
template
>
<el-skeleton
:loading=
"loading"
animated
>
<el-row>
<el-col
v-for=
"(item, index) in projects"
:key=
"`card-${index}`"
:xl=
"8"
:lg=
"8"
:md=
"8"
:sm=
"24"
:xs=
"24"
>
<el-card
shadow=
"hover"
class=
"mr-5px mt-5px cursor-pointer"
@
click=
"handleProjectClick(item.message)"
>
<div
class=
"flex items-center"
>
<Icon
:icon=
"item.icon"
:size=
"25"
class=
"mr-8px"
:style=
"{ color: item.color }"
/>
<span
class=
"text-16px"
>
{{ item.name }}
</span>
</div>
<div
class=
"mt-12px text-12px text-gray-400"
>
{{ t(item.message) }}
</div>
<div
class=
"mt-12px flex justify-between text-12px text-gray-400"
>
<span>
{{ item.personal }}
</span>
<span>
{{ formatTime(item.time, 'yyyy-MM-dd') }}
</span>
</div>
</el-card>
</el-col>
</el-row>
</el-skeleton>
</el-card>
<el-card
shadow=
"never"
class=
"mt-8px"
>
<el-skeleton
:loading=
"loading"
animated
>
<el-row
:gutter=
"20"
justify=
"space-between"
>
<el-col
:xl=
"10"
:lg=
"10"
:md=
"24"
:sm=
"24"
:xs=
"24"
>
<el-card
shadow=
"hover"
class=
"mb-8px"
>
<el-skeleton
:loading=
"loading"
animated
>
<Echart
:options=
"pieOptionsData"
:height=
"280"
/>
</el-skeleton>
</el-card>
</el-col>
<el-col
:xl=
"14"
:lg=
"14"
:md=
"24"
:sm=
"24"
:xs=
"24"
>
<el-card
shadow=
"hover"
class=
"mb-8px"
>
<el-skeleton
:loading=
"loading"
animated
>
<Echart
:options=
"barOptionsData"
:height=
"280"
/>
</el-skeleton>
</el-card>
</el-col>
</el-row>
</el-skeleton>
</el-card>
</el-col>
<el-col
:xl=
"8"
:lg=
"8"
:md=
"24"
:sm=
"24"
:xs=
"24"
class=
"mb-8px"
>
<el-card
shadow=
"never"
>
<
template
#header
>
<div
class=
"h-3 flex justify-between"
>
<span>
{{
t
(
'
workplace.shortcutOperation
'
)
}}
</span>
</div>
</
template
>
<el-skeleton
:loading=
"loading"
animated
>
<el-row>
<el-col
v-for=
"item in shortcut"
:key=
"`team-${item.name}`"
:span=
"8"
class=
"mb-8px"
>
<div
class=
"flex items-center"
>
<Icon
:icon=
"item.icon"
class=
"mr-8px"
:style=
"{ color: item.color }"
/>
<el-link
type=
"default"
:underline=
"false"
@
click=
"handleShortcutClick(item.url)"
>
{{ item.name }}
</el-link>
</div>
</el-col>
</el-row>
</el-skeleton>
</el-card>
<el-card
shadow=
"never"
class=
"mt-8px"
>
<
template
#header
>
<div
class=
"h-3 flex justify-between"
>
<span>
{{
t
(
'
workplace.notice
'
)
}}
</span>
<el-link
type=
"primary"
:underline=
"false"
>
{{
t
(
'
action.more
'
)
}}
</el-link>
</div>
</
template
>
<el-skeleton
:loading=
"loading"
animated
>
<div
v-for=
"(item, index) in notice"
:key=
"`dynamics-${index}`"
>
<div
class=
"flex items-center"
>
<el-avatar
:src=
"avatar"
:size=
"35"
class=
"mr-16px"
>
<img
src=
"@/assets/imgs/logo.png"
alt=
""
/>
</el-avatar>
<div>
<div
class=
"text-14px"
>
<Highlight
:keys=
"item.keys.map((v) => t(v))"
>
{{ item.type }} : {{ item.title }}
</Highlight>
</div>
<div
class=
"mt-16px text-12px text-gray-400"
>
{{ formatTime(item.date, 'yyyy-MM-dd') }}
</div>
</div>
</div>
<el-divider
/>
</div>
</el-skeleton>
</el-card>
</el-col>
</el-row>
</template>
<
script
lang=
"ts"
setup
>
import
{
set
}
from
'
lodash-es
'
import
{
EChartsOption
}
from
'
echarts
'
import
{
formatTime
}
from
'
@/utils
'
import
{
useUserStore
}
from
'
@/store/modules/user
'
// import { useWatermark } from '@/hooks/web/useWatermark'
import
type
{
WorkplaceTotal
,
Project
,
Notice
,
Shortcut
}
from
'
./types
'
import
{
pieOptions
,
barOptions
}
from
'
./echarts-data
'
import
{
useRouter
}
from
'
vue-router
'
defineOptions
({
name
:
'
Index
'
})
const
{
t
}
=
useI18n
()
const
router
=
useRouter
()
const
userStore
=
useUserStore
()
// const { setWatermark } = useWatermark()
const
loading
=
ref
(
true
)
const
avatar
=
userStore
.
getUser
.
avatar
const
username
=
userStore
.
getUser
.
nickname
const
pieOptionsData
=
reactive
<
EChartsOption
>
(
pieOptions
)
as
EChartsOption
// 获取统计数
let
totalSate
=
reactive
<
WorkplaceTotal
>
({
project
:
0
,
access
:
0
,
todo
:
0
})
const
getCount
=
async
()
=>
{
const
data
=
{
project
:
40
,
access
:
2340
,
todo
:
10
}
totalSate
=
Object
.
assign
(
totalSate
,
data
)
}
// 获取项目数
let
projects
=
reactive
<
Project
[]
>
([])
const
getProject
=
async
()
=>
{
const
data
=
[
{
name
:
'
ruoyi-vue-pro
'
,
icon
:
'
simple-icons:springboot
'
,
message
:
'
github.com/YunaiV/ruoyi-vue-pro
'
,
personal
:
'
Spring Boot 单体架构
'
,
time
:
new
Date
(
'
2025-01-02
'
),
color
:
'
#6DB33F
'
},
{
name
:
'
yudao-ui-admin-vue3
'
,
icon
:
'
ep:element-plus
'
,
message
:
'
github.com/yudaocode/yudao-ui-admin-vue3
'
,
personal
:
'
Vue3 + element-plus 管理后台
'
,
time
:
new
Date
(
'
2025-02-03
'
),
color
:
'
#409EFF
'
},
{
name
:
'
yudao-ui-mall-uniapp
'
,
icon
:
'
icon-park-outline:mall-bag
'
,
message
:
'
github.com/yudaocode/yudao-ui-mall-uniapp
'
,
personal
:
'
Vue3 + uniapp 商城手机端
'
,
time
:
new
Date
(
'
2025-03-04
'
),
color
:
'
#ff4d4f
'
},
{
name
:
'
yudao-cloud
'
,
icon
:
'
material-symbols:cloud-outline
'
,
message
:
'
github.com/YunaiV/yudao-cloud
'
,
personal
:
'
Spring Cloud 微服务架构
'
,
time
:
new
Date
(
'
2025-04-05
'
),
color
:
'
#1890ff
'
},
{
name
:
'
yudao-ui-admin-vben
'
,
icon
:
'
devicon:antdesign
'
,
message
:
'
github.com/yudaocode/yudao-ui-admin-vben
'
,
personal
:
'
Vue3 + vben5(antd) 管理后台
'
,
time
:
new
Date
(
'
2025-05-06
'
),
color
:
'
#e18525
'
},
{
name
:
'
yudao-ui-admin-uniapp
'
,
icon
:
'
ant-design:mobile
'
,
message
:
'
github.com/yudaocode/yudao-ui-admin-uniapp
'
,
personal
:
'
Vue3 + uniapp 管理手机端
'
,
time
:
new
Date
(
'
2025-06-01
'
),
color
:
'
#2979ff
'
}
]
projects
=
Object
.
assign
(
projects
,
data
)
}
// 获取通知公告
let
notice
=
reactive
<
Notice
[]
>
([])
const
getNotice
=
async
()
=>
{
const
data
=
[
{
title
:
'
系统支持 JDK 8/17/21,Vue 2/3
'
,
type
:
'
技术兼容性
'
,
keys
:
[
'
JDK
'
,
'
Vue
'
],
date
:
new
Date
()
},
{
title
:
'
后端提供 Spring Boot 2.7/3.2 + Cloud 双架构
'
,
type
:
'
架构灵活性
'
,
keys
:
[
'
Boot
'
,
'
Cloud
'
],
date
:
new
Date
()
},
{
title
:
'
全部开源,个人与企业可 100% 直接使用,无需授权
'
,
type
:
'
开源免授权
'
,
keys
:
[
'
无需授权
'
],
date
:
new
Date
()
},
{
title
:
'
国内使用最广泛的快速开发平台,远超 10w+ 企业使用
'
,
type
:
'
广泛企业认可
'
,
keys
:
[
'
最广泛
'
,
'
10w+
'
],
date
:
new
Date
()
}
]
notice
=
Object
.
assign
(
notice
,
data
)
}
// 获取快捷入口
let
shortcut
=
reactive
<
Shortcut
[]
>
([])
const
getShortcut
=
async
()
=>
{
const
data
=
[
{
name
:
'
首页
'
,
icon
:
'
ion:home-outline
'
,
url
:
'
/
'
,
color
:
'
#1fdaca
'
},
{
name
:
'
商城中心
'
,
icon
:
'
ep:shop
'
,
url
:
'
/mall/home
'
,
color
:
'
#ff6b6b
'
},
{
name
:
'
AI 大模型
'
,
icon
:
'
tabler:ai
'
,
url
:
'
/ai/chat
'
,
color
:
'
#7c3aed
'
},
{
name
:
'
ERP 系统
'
,
icon
:
'
simple-icons:erpnext
'
,
url
:
'
/erp/home
'
,
color
:
'
#3fb27f
'
},
{
name
:
'
CRM 系统
'
,
icon
:
'
simple-icons:civicrm
'
,
url
:
'
/crm/backlog
'
,
color
:
'
#4daf1bc9
'
},
{
name
:
'
IoT 物联网
'
,
icon
:
'
fa-solid:hdd
'
,
url
:
'
/iot/home
'
,
color
:
'
#1a73e8
'
}
]
shortcut
=
Object
.
assign
(
shortcut
,
data
)
}
// 用户来源
const
getUserAccessSource
=
async
()
=>
{
const
data
=
[
{
value
:
335
,
name
:
'
analysis.directAccess
'
},
{
value
:
310
,
name
:
'
analysis.mailMarketing
'
},
{
value
:
234
,
name
:
'
analysis.allianceAdvertising
'
},
{
value
:
135
,
name
:
'
analysis.videoAdvertising
'
},
{
value
:
1548
,
name
:
'
analysis.searchEngines
'
}
]
set
(
pieOptionsData
,
'
legend.data
'
,
data
.
map
((
v
)
=>
t
(
v
.
name
))
)
pieOptionsData
!
.
series
!
[
0
].
data
=
data
.
map
((
v
)
=>
{
return
{
name
:
t
(
v
.
name
),
value
:
v
.
value
}
})
}
const
barOptionsData
=
reactive
<
EChartsOption
>
(
barOptions
)
as
EChartsOption
// 周活跃量
const
getWeeklyUserActivity
=
async
()
=>
{
const
data
=
[
{
value
:
13253
,
name
:
'
analysis.monday
'
},
{
value
:
34235
,
name
:
'
analysis.tuesday
'
},
{
value
:
26321
,
name
:
'
analysis.wednesday
'
},
{
value
:
12340
,
name
:
'
analysis.thursday
'
},
{
value
:
24643
,
name
:
'
analysis.friday
'
},
{
value
:
1322
,
name
:
'
analysis.saturday
'
},
{
value
:
1324
,
name
:
'
analysis.sunday
'
}
]
set
(
barOptionsData
,
'
xAxis.data
'
,
data
.
map
((
v
)
=>
t
(
v
.
name
))
)
set
(
barOptionsData
,
'
series
'
,
[
{
name
:
t
(
'
analysis.activeQuantity
'
),
data
:
data
.
map
((
v
)
=>
v
.
value
),
type
:
'
bar
'
}
])
}
const
getAllApi
=
async
()
=>
{
await
Promise
.
all
([
getCount
(),
getProject
(),
getNotice
(),
getShortcut
(),
getUserAccessSource
(),
getWeeklyUserActivity
()
])
loading
.
value
=
false
}
const
handleProjectClick
=
(
message
:
string
)
=>
{
window
.
open
(
`https://
${
message
}
`
,
'
_blank
'
)
}
const
handleShortcutClick
=
(
url
:
string
)
=>
{
router
.
push
(
url
)
}
getAllApi
()
</
script
>
yudao-ui/yudao-ui-admin-vue3/src/views/Home/Index.vue
View file @
6a3f72ad
<
template
>
<
template
>
<div>
<div
v-loading=
"loading"
>
<el-card
shadow=
"never"
>
<div
class=
"data-board"
>
<el-skeleton
:loading=
"loading"
animated
>
<!-- 标题与提示 -->
<el-row
:gutter=
"16"
justify=
"space-between"
>
<div
class=
"board-header"
>
数据统计-大数据看板
</div>
<el-col
:xl=
"12"
:lg=
"12"
:md=
"12"
:sm=
"24"
:xs=
"24"
>
<!-- 筛选区域 -->
<div
class=
"flex items-center"
>
<div
class=
"filter-bar"
>
<el-avatar
:src=
"avatar"
:size=
"70"
class=
"mr-16px"
>
<!-- 客户关键词搜索 -->
<img
src=
"@/assets/imgs/logo.png"
alt=
""
/>
<el-input
</el-avatar>
v-model=
"searchKey"
<div>
@
blur=
"getEcharts"
<div
class=
"text-20px"
>
placeholder=
"请输入客户关键词"
{{
t
(
'
workplace.welcome
'
)
}}
{{
username
}}
{{
t
(
'
workplace.happyDay
'
)
}}
clearable
</div>
class=
"filter-item"
<div
class=
"mt-10px text-14px text-gray-500"
>
>
{{
t
(
'
workplace.toady
'
)
}}
,20℃ - 32℃!
<template
#suffix
>
</div>
<i
class=
"el-icon-arrow-down"
@
click=
"openSearchDropdown"
></i>
</div>
</
template
>
</div>
</el-input>
</el-col>
<el-col
:xl=
"12"
:lg=
"12"
:md=
"12"
:sm=
"24"
:xs=
"24"
>
<!-- 统计维度筛选 -->
<div
class=
"h-70px flex items-center justify-end lt-sm:mt-10px"
>
<el-select
<div
class=
"px-8px text-right"
>
v-model=
"statType"
<div
class=
"mb-16px text-14px text-gray-400"
>
{{
t
(
'
workplace.project
'
)
}}
</div>
placeholder=
"请选择"
<CountTo
class=
"filter-item"
class=
"text-20px"
@
change=
"handleStatChange"
:start-val=
"0"
>
:end-val=
"totalSate.project"
<el-option
label=
"请选择"
value=
""
/>
:duration=
"2600"
<el-option
label=
"年统计"
value=
"year"
/>
<el-option
label=
"月统计"
value=
"month"
/>
<el-option
label=
"日统计"
value=
"day"
/>
</el-select>
<!-- 时间范围选择 -->
<el-date-picker
v-model=
"timeRange"
:type=
"pickerType"
range-separator=
"至"
:start-placeholder=
"startPlaceholderype"
:end-placeholder=
"endPlaceholder"
:format=
"format"
:value-format=
"valueFormat"
class=
"filter-item"
@
change=
"getPickerValue"
/>
/>
</div>
</div>
<el-divider
direction=
"vertical"
/>
<div
class=
"px-8px text-right"
>
<div
class=
"mb-16px text-14px text-gray-400"
>
{{
t
(
'
workplace.toDo
'
)
}}
</div>
<CountTo
class=
"text-20px"
:start-val=
"0"
:end-val=
"totalSate.todo"
:duration=
"2600"
/>
</div>
</div>
<el-divider
direction=
"vertical"
border-style=
"dashed"
/>
<card
ref=
"cardRef"
:homeInfoFirst=
"homeInfoFirst"
/>
<div
class=
"px-8px text-right"
>
<div
class=
"page-container"
>
<div
class=
"mb-16px text-14px text-gray-400"
>
{{
t
(
'
workplace.access
'
)
}}
</div>
<div
class=
"top top_1"
>
<CountTo
<div>
class=
"text-20px"
<BarChart
:start-val=
"0"
:title=
"homeInfoBfztjVal"
:end-val=
"totalSate.access"
:xData=
"homeInfoBfztj?.weeks"
:duration=
"2600"
:yData=
"homeInfoBfztj?.visitCounts"
seriesName=
"拜访量"
color=
"#a9d672"
height=
"350px"
width=
"500px"
/>
/>
</div>
</div>
<div>
<LineChart
:title=
"homeInfoBfrtjVal"
:xData=
"homeInfoBfrtj?.dates"
:yData=
"homeInfoBfrtj?.counts"
seriesName=
"访问量"
color=
"#5793f3"
height=
"350px"
/>
</div>
</div>
</el-col>
</el-row>
</el-skeleton>
</el-card>
</div>
</div>
<el-row
class=
"mt-8px"
:gutter=
"8"
justify=
"space-between"
>
<div
class=
"top top_2"
>
<el-col
:xl=
"16"
:lg=
"16"
:md=
"24"
:sm=
"24"
:xs=
"24"
class=
"mb-8px"
>
<div>
<el-card
shadow=
"never"
>
<PieChart
<template
#header
>
:data=
"homeInfoKhxzdjzbqk"
<div
class=
"h-3 flex justify-between"
>
:total=
"200"
<span>
{{
t
(
'
workplace.project
'
)
}}
</span>
tooltipTip=
"客户性质等级占比情况"
<el-link
width=
"500px"
type=
"primary"
:underline=
"false"
href=
"https://github.com/yudaocode"
target=
"_blank"
>
{{
t
(
'
action.more
'
)
}}
</el-link>
</div>
</
template
>
<el-skeleton
:loading=
"loading"
animated
>
<el-row>
<el-col
v-for=
"(item, index) in projects"
:key=
"`card-${index}`"
:xl=
"8"
:lg=
"8"
:md=
"8"
:sm=
"24"
:xs=
"24"
>
<el-card
shadow=
"hover"
class=
"mr-5px mt-5px cursor-pointer"
@
click=
"handleProjectClick(item.message)"
>
<div
class=
"flex items-center"
>
<Icon
:icon=
"item.icon"
:size=
"25"
class=
"mr-8px"
:style=
"{ color: item.color }"
/>
/>
<span
class=
"text-16px"
>
{{ item.name }}
</span>
</div>
<div
class=
"mt-12px text-12px text-gray-400"
>
{{ t(item.message) }}
</div>
<div
class=
"mt-12px flex justify-between text-12px text-gray-400"
>
<span>
{{ item.personal }}
</span>
<span>
{{ formatTime(item.time, 'yyyy-MM-dd') }}
</span>
</div>
</div>
</el-card>
<div>
</el-col>
<BarChart
_1
</el-row>
:data=
"homeInfoBfrjfbqk"
</el-skeleton>
title=
"拜访人均分布情况"
</el-card>
width=
"600px"
height=
"400px"
<el-card
shadow=
"never"
class=
"mt-8px"
>
/>
<el-skeleton
:loading=
"loading"
animated
>
<el-row
:gutter=
"20"
justify=
"space-between"
>
<el-col
:xl=
"10"
:lg=
"10"
:md=
"24"
:sm=
"24"
:xs=
"24"
>
<el-card
shadow=
"hover"
class=
"mb-8px"
>
<el-skeleton
:loading=
"loading"
animated
>
<Echart
:options=
"pieOptionsData"
:height=
"280"
/>
</el-skeleton>
</el-card>
</el-col>
<el-col
:xl=
"14"
:lg=
"14"
:md=
"24"
:sm=
"24"
:xs=
"24"
>
<el-card
shadow=
"hover"
class=
"mb-8px"
>
<el-skeleton
:loading=
"loading"
animated
>
<Echart
:options=
"barOptionsData"
:height=
"280"
/>
</el-skeleton>
</el-card>
</el-col>
</el-row>
</el-skeleton>
</el-card>
</el-col>
<el-col
:xl=
"8"
:lg=
"8"
:md=
"24"
:sm=
"24"
:xs=
"24"
class=
"mb-8px"
>
<el-card
shadow=
"never"
>
<
template
#header
>
<div
class=
"h-3 flex justify-between"
>
<span>
{{
t
(
'
workplace.shortcutOperation
'
)
}}
</span>
</div>
</div>
</
template
>
<el-skeleton
:loading=
"loading"
animated
>
<el-row>
<el-col
v-for=
"item in shortcut"
:key=
"`team-${item.name}`"
:span=
"8"
class=
"mb-8px"
>
<div
class=
"flex items-center"
>
<Icon
:icon=
"item.icon"
class=
"mr-8px"
:style=
"{ color: item.color }"
/>
<el-link
type=
"default"
:underline=
"false"
@
click=
"handleShortcutClick(item.url)"
>
{{ item.name }}
</el-link>
</div>
</div>
</el-col>
<div
class=
"top top_3"
>
</el-row>
<div>
</el-skeleton>
<PieChart
</el-card>
:data=
"homeInfoKhbffszbqk"
<el-card
shadow=
"never"
class=
"mt-8px"
>
:total=
"200"
<
template
#header
>
tooltipTip=
"客户拜访方式占比情况"
<div
class=
"h-3 flex justify-between"
>
width=
"500px"
<span>
{{
t
(
'
workplace.notice
'
)
}}
</span>
/>
<el-link
type=
"primary"
:underline=
"false"
>
{{
t
(
'
action.more
'
)
}}
</el-link>
</div>
</div>
</
template
>
<el-skeleton
:loading=
"loading"
animated
>
<div
v-for=
"(item, index) in notice"
:key=
"`dynamics-${index}`"
>
<div
class=
"flex items-center"
>
<el-avatar
:src=
"avatar"
:size=
"35"
class=
"mr-16px"
>
<img
src=
"@/assets/imgs/logo.png"
alt=
""
/>
</el-avatar>
<div>
<div>
<div
class=
"text-14px"
>
<PieChart
<Highlight
:keys=
"item.keys.map((v) => t(v))"
>
:data=
"homeInfoKhbmzbqk"
{{ item.type }} : {{ item.title }}
:total=
"200"
</Highlight>
tooltipTip=
"客户部门占比情况"
width=
"500px"
/>
</div>
</div>
<div
class=
"mt-16px text-12px text-gray-400"
>
<div>
{{ formatTime(item.date, 'yyyy-MM-dd') }}
<PieChart
:data=
"homeInfoKhbflxzbqk"
:total=
"200"
tooltipTip=
"客户拜访类型占比情况"
width=
"500px"
/>
</div>
</div>
</div>
</div>
<BarChart
_1
:data=
"homeInfoBfcplxzbqk"
title=
"拜访产品类型占比情况"
width=
"1400px"
height=
"400px"
/>
</div>
</div>
<el-divider
/>
</div>
</div>
</el-skeleton>
</el-card>
</el-col>
</el-row>
</template>
</template>
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
set
}
from
'
lodash-es
'
import
{
ref
,
onMounted
}
from
'
vue
'
import
{
EChartsOption
}
from
'
echarts
'
import
card
from
'
./card.vue
'
import
{
formatTime
}
from
'
@/utils
'
// import chartsCard from './chartsCard.vue'
import
{
homesApi
}
from
'
@/api/visit/home
'
import
BarChart
from
'
./components/BarChart.vue
'
import
BarChart_1
from
'
./components/barChart_1.vue
'
import
LineChart
from
'
./components/LineChart.vue
'
import
PieChart
from
'
./components/PieChart.vue
'
import
{
log
}
from
'
console
'
const
searchKey
=
ref
(
''
)
const
statType
=
ref
(
''
)
const
timeRange
=
ref
<
[
string
,
string
]
|
[]
>
([])
// 实际存储时间戳或标准格式
const
pickerType
=
ref
(
'
daterange
'
)
const
startPlaceholderype
=
ref
(
'
开始日期
'
)
const
endPlaceholder
=
ref
(
'
结束日期
'
)
const
format
=
ref
(
'
YYYY年MM月DD日
'
)
const
valueFormat
=
ref
(
'
YYYY-MM-DD
'
)
// const pieData = ref([
// { name: '电话', value: 765, color: '#5793f3' },
// { name: '上门', value: 560, color: '#a9d672' },
// { name: '社交网络', value: 400, color: '#fac858' },
// { name: '推广渠道', value: 200, color: '#f96c6a' },
// { name: '其他', value: 120, color: '#c2a6f2' }
// ])
// const chartData_1 = ref([
// { name: '业务员1', value: 78, color: '#fcd36e' },
// { name: '业务员2', value: 68, color: '#f28b8b' },
// { name: '业务员3', value: 50, color: '#a491f2' },
// { name: '业务员4', value: 42, color: '#fbbc58' },
// { name: '业务员5', value: 32, color: '#7ecb5c' },
// { name: '业务员6', value: 30, color: '#68c0cf' },
// { name: '业务员7', value: 28, color: '#66b1fc' }
// ])
const
handleStatChange
=
(
val
:
string
)
=>
{
console
.
log
(
'
切换统计维度:
'
,
val
)
timeRange
.
value
=
[]
if
(
val
==
'
year
'
)
{
pickerType
.
value
=
'
yearrange
'
startPlaceholderype
.
value
=
'
开始年份
'
endPlaceholder
.
value
=
'
结束年份
'
format
.
value
=
'
YYYY年
'
valueFormat
.
value
=
'
YYYY
'
}
else
if
(
val
==
'
month
'
)
{
pickerType
.
value
=
'
monthrange
'
startPlaceholderype
.
value
=
'
开始月份
'
endPlaceholder
.
value
=
'
结束月份
'
format
.
value
=
'
YYYY年MM月
'
valueFormat
.
value
=
'
YYYY-MM
'
}
else
{
pickerType
.
value
=
'
daterange
'
startPlaceholderype
.
value
=
'
开始日期
'
endPlaceholder
.
value
=
'
结束日期
'
format
.
value
=
'
YYYY年MM月DD日
'
valueFormat
.
value
=
'
YYYY-MM-DD
'
}
// 调用接口,根据 statType 和 timeRange 获取数据
getEcharts
()
}
const
openSearchDropdown
=
()
=>
{
// 打开下拉菜单(如筛选条件、历史记录等)
console
.
log
(
'
打开搜索下拉
'
)
}
// 获取日期
const
getPickerValue
=
(
e
:
any
)
=>
{
console
.
log
(
e
)
}
// 图表数据
// 加载状态
const
loading
=
ref
(
false
)
// 错误信息
const
error
=
ref
(
''
)
import
{
useUserStore
}
from
'
@/store/modules/user
'
// 各图表数据存储
// import { useWatermark } from '@/hooks/web/useWatermark'
const
homeInfoFirst
:
Ref
<
any
|
null
>
=
ref
(
null
)
import
type
{
WorkplaceTotal
,
Project
,
Notice
,
Shortcut
}
from
'
./types
'
const
homeInfoBfztj
:
Ref
<
any
|
null
>
=
ref
(
null
)
import
{
pieOptions
,
barOptions
}
from
'
./echarts-data
'
const
homeInfoBfrtj
:
Ref
<
any
|
null
>
=
ref
(
null
)
import
{
useRouter
}
from
'
vue-router
'
const
homeInfoKhxzdjzbqk
:
Ref
<
any
|
null
>
=
ref
([])
const
homeInfoBfrjfbqk
:
Ref
<
any
|
null
>
=
ref
([])
const
homeInfoKhbffszbqk
:
Ref
<
any
|
null
>
=
ref
([])
const
homeInfoKhbmzbqk
:
Ref
<
any
|
null
>
=
ref
([])
const
homeInfoKhbflxzbqk
:
Ref
<
any
|
null
>
=
ref
([])
const
homeInfoBfcplxzbqk
:
Ref
<
any
|
null
>
=
ref
([])
const
homeInfoBfztjVal
=
ref
(
'
拜访日统计
'
)
const
homeInfoBfrtjVal
=
ref
(
'
拜访日统计
'
)
// 获取图表数据
const
getEcharts
=
async
()
=>
{
error
.
value
=
''
let
params
:
any
=
{
companyName
:
searchKey
.
value
,
searchTimeString
:
timeRange
.
value
}
try
{
// 卡片数据
homesApi
.
getHomeInfoFirst
(
params
)
.
then
((
res
:
any
)
=>
{
homeInfoFirst
.
value
=
res
console
.
log
(
'
卡片数据:
'
,
res
)
})
.
catch
((
err
)
=>
{
console
.
error
(
'
卡片数据获取失败:
'
,
err
)
error
.
value
=
'
卡片数据获取失败
'
})
defineOptions
({
name
:
'
Index
'
})
// 竖柱状图数据
homesApi
.
getHomeInfoBfztj
(
params
)
.
then
((
res
:
any
)
=>
{
console
.
log
(
'
竖柱状图数据:
'
,
res
)
homeInfoBfztj
.
value
=
res
})
.
catch
((
err
)
=>
{
console
.
error
(
'
竖柱状图数据获取失败:
'
,
err
)
error
.
value
=
'
竖柱状图数据获取失败
'
})
const
{
t
}
=
useI18n
()
// 折线图数据
const
router
=
useRouter
()
homesApi
const
userStore
=
useUserStore
()
.
getHomeInfoBfrtj
(
params
)
// const { setWatermark } = useWatermark()
.
then
((
res
:
any
)
=>
{
const
loading
=
ref
(
true
)
console
.
log
(
'
折线图数据:
'
,
res
)
const
avatar
=
userStore
.
getUser
.
avatar
homeInfoBfrtj
.
value
=
res
const
username
=
userStore
.
getUser
.
nickname
})
const
pieOptionsData
=
reactive
<
EChartsOption
>
(
pieOptions
)
as
EChartsOption
.
catch
((
err
)
=>
{
// 获取统计数
console
.
error
(
'
折线图数据获取失败:
'
,
err
)
let
totalSate
=
reactive
<
WorkplaceTotal
>
({
error
.
value
=
'
折线图数据获取失败
'
project
:
0
,
})
access
:
0
,
todo
:
0
})
const
getCount
=
async
()
=>
{
// 客户性质等级占比情况
const
data
=
{
homesApi
project
:
40
,
.
getHomeInfoKhxzdjzbqk
(
params
)
access
:
2340
,
.
then
((
res
:
any
)
=>
{
todo
:
10
homeInfoKhxzdjzbqk
.
value
=
res
.
list
}
console
.
log
(
'
客户性质等级占比:
'
,
res
.
list
,
homeInfoKhxzdjzbqk
)
totalSate
=
Object
.
assign
(
totalSate
,
data
)
})
}
.
catch
((
err
)
=>
{
console
.
error
(
'
客户性质等级占比获取失败:
'
,
err
)
error
.
value
=
'
客户性质等级占比获取失败
'
})
// 获取项目数
// 拜访人均分布情况
let
projects
=
reactive
<
Project
[]
>
([])
homesApi
const
getProject
=
async
()
=>
{
.
getHomeInfoBfrjfbqk
(
params
)
const
data
=
[
.
then
((
res
:
any
)
=>
{
{
console
.
log
(
'
拜访人均分布:
'
,
res
)
name
:
'
ruoyi-vue-pro
'
,
homeInfoBfrjfbqk
.
value
=
res
.
list
icon
:
'
simple-icons:springboot
'
,
})
message
:
'
github.com/YunaiV/ruoyi-vue-pro
'
,
.
catch
((
err
)
=>
{
personal
:
'
Spring Boot 单体架构
'
,
console
.
error
(
'
拜访人均分布获取失败:
'
,
err
)
time
:
new
Date
(
'
2025-01-02
'
),
error
.
value
=
'
拜访人均分布获取失败
'
color
:
'
#6DB33F
'
})
},
{
name
:
'
yudao-ui-admin-vue3
'
,
icon
:
'
ep:element-plus
'
,
message
:
'
github.com/yudaocode/yudao-ui-admin-vue3
'
,
personal
:
'
Vue3 + element-plus 管理后台
'
,
time
:
new
Date
(
'
2025-02-03
'
),
color
:
'
#409EFF
'
},
{
name
:
'
yudao-ui-mall-uniapp
'
,
icon
:
'
icon-park-outline:mall-bag
'
,
message
:
'
github.com/yudaocode/yudao-ui-mall-uniapp
'
,
personal
:
'
Vue3 + uniapp 商城手机端
'
,
time
:
new
Date
(
'
2025-03-04
'
),
color
:
'
#ff4d4f
'
},
{
name
:
'
yudao-cloud
'
,
icon
:
'
material-symbols:cloud-outline
'
,
message
:
'
github.com/YunaiV/yudao-cloud
'
,
personal
:
'
Spring Cloud 微服务架构
'
,
time
:
new
Date
(
'
2025-04-05
'
),
color
:
'
#1890ff
'
},
{
name
:
'
yudao-ui-admin-vben
'
,
icon
:
'
devicon:antdesign
'
,
message
:
'
github.com/yudaocode/yudao-ui-admin-vben
'
,
personal
:
'
Vue3 + vben5(antd) 管理后台
'
,
time
:
new
Date
(
'
2025-05-06
'
),
color
:
'
#e18525
'
},
{
name
:
'
yudao-ui-admin-uniapp
'
,
icon
:
'
ant-design:mobile
'
,
message
:
'
github.com/yudaocode/yudao-ui-admin-uniapp
'
,
personal
:
'
Vue3 + uniapp 管理手机端
'
,
time
:
new
Date
(
'
2025-06-01
'
),
color
:
'
#2979ff
'
}
]
projects
=
Object
.
assign
(
projects
,
data
)
}
// 获取通知公告
// 客户拜访方式占比情况
let
notice
=
reactive
<
Notice
[]
>
([])
homesApi
const
getNotice
=
async
()
=>
{
.
getHomeInfoKhbffszbqk
(
params
)
const
data
=
[
.
then
((
res
:
any
)
=>
{
{
console
.
log
(
'
客户拜访方式占比:
'
,
res
)
title
:
'
系统支持 JDK 8/17/21,Vue 2/3
'
,
homeInfoKhbffszbqk
.
value
=
res
.
list
type
:
'
技术兼容性
'
,
})
keys
:
[
'
JDK
'
,
'
Vue
'
],
.
catch
((
err
)
=>
{
date
:
new
Date
()
console
.
error
(
'
客户拜访方式占比获取失败:
'
,
err
)
},
error
.
value
=
'
客户拜访方式占比获取失败
'
{
})
title
:
'
后端提供 Spring Boot 2.7/3.2 + Cloud 双架构
'
,
type
:
'
架构灵活性
'
,
// 客户部门占比情况
keys
:
[
'
Boot
'
,
'
Cloud
'
],
homesApi
date
:
new
Date
()
.
getHomeInfoKhbmzbqk
(
params
)
},
.
then
((
res
:
any
)
=>
{
{
console
.
log
(
'
客户部门占比:
'
,
res
)
title
:
'
全部开源,个人与企业可 100% 直接使用,无需授权
'
,
homeInfoKhbmzbqk
.
value
=
res
.
list
type
:
'
开源免授权
'
,
})
keys
:
[
'
无需授权
'
],
.
catch
((
err
)
=>
{
date
:
new
Date
()
console
.
error
(
'
客户部门占比获取失败:
'
,
err
)
},
error
.
value
=
'
客户部门占比获取失败
'
{
})
title
:
'
国内使用最广泛的快速开发平台,远超 10w+ 企业使用
'
,
type
:
'
广泛企业认可
'
,
keys
:
[
'
最广泛
'
,
'
10w+
'
],
date
:
new
Date
()
}
]
notice
=
Object
.
assign
(
notice
,
data
)
}
// 获取快捷入口
// 客户拜访类型占比情况
let
shortcut
=
reactive
<
Shortcut
[]
>
([])
homesApi
.
getHomeInfoKhbflxzbqk
(
params
)
.
then
((
res
:
any
)
=>
{
console
.
log
(
'
客户拜访类型占比:
'
,
res
)
homeInfoKhbflxzbqk
.
value
=
res
.
list
})
.
catch
((
err
)
=>
{
console
.
error
(
'
客户拜访类型占比获取失败:
'
,
err
)
error
.
value
=
'
客户拜访类型占比获取失败
'
})
// 拜访产品类型占比情况
homesApi
.
getHomeInfoBfcplxzbqk
(
params
)
.
then
((
res
:
any
)
=>
{
console
.
log
(
'
拜访产品类型占比:
'
,
res
)
homeInfoBfcplxzbqk
.
value
=
res
.
list
})
.
catch
((
err
)
=>
{
console
.
error
(
'
拜访产品类型占比获取失败:
'
,
err
)
error
.
value
=
'
拜访产品类型占比获取失败
'
})
const
getShortcut
=
async
()
=>
{
loading
.
value
=
true
const
data
=
[
}
catch
(
err
)
{
{
console
.
error
(
'
数据获取出错:
'
,
err
)
name
:
'
首页
'
,
error
.
value
=
'
数据获取出错,请重试
'
icon
:
'
ion:home-outline
'
,
}
finally
{
url
:
'
/
'
,
loading
.
value
=
false
color
:
'
#1fdaca
'
},
{
name
:
'
商城中心
'
,
icon
:
'
ep:shop
'
,
url
:
'
/mall/home
'
,
color
:
'
#ff6b6b
'
},
{
name
:
'
AI 大模型
'
,
icon
:
'
tabler:ai
'
,
url
:
'
/ai/chat
'
,
color
:
'
#7c3aed
'
},
{
name
:
'
ERP 系统
'
,
icon
:
'
simple-icons:erpnext
'
,
url
:
'
/erp/home
'
,
color
:
'
#3fb27f
'
},
{
name
:
'
CRM 系统
'
,
icon
:
'
simple-icons:civicrm
'
,
url
:
'
/crm/backlog
'
,
color
:
'
#4daf1bc9
'
},
{
name
:
'
IoT 物联网
'
,
icon
:
'
fa-solid:hdd
'
,
url
:
'
/iot/home
'
,
color
:
'
#1a73e8
'
}
}
]
shortcut
=
Object
.
assign
(
shortcut
,
data
)
}
}
onMounted
(()
=>
{
// 用户来源
getEcharts
()
const
getUserAccessSource
=
async
()
=>
{
})
const
data
=
[
watch
(
timeRange
,
(
newVal
:
any
)
=>
{
{
value
:
335
,
name
:
'
analysis.directAccess
'
},
getEcharts
()
{
value
:
310
,
name
:
'
analysis.mailMarketing
'
},
})
{
value
:
234
,
name
:
'
analysis.allianceAdvertising
'
},
</
script
>
{
value
:
135
,
name
:
'
analysis.videoAdvertising
'
},
<
style
scoped
lang=
"less"
>
{
value
:
1548
,
name
:
'
analysis.searchEngines
'
}
/* 自定义 Element Plus 组件样式 */
]
.filter-item {
set
(
width: 200px;
pieOptionsData
,
}
'
legend.data
'
,
.data-board {
data
.
map
((
v
)
=>
t
(
v
.
name
))
display: flex;
)
.board-header {
pieOptionsData
!
.
series
!
[
0
].
data
=
data
.
map
((
v
)
=>
{
margin-right: 200px;
return
{
name
:
t
(
v
.
name
),
value
:
v
.
value
}
}
})
.filter-bar {
display: flex;
align-items: center;
// justify-content: space-around;
width: 70%;
.el-date-editor {
margin-left: 30px;
flex: none;
}
}
}
.page-container {
padding: 20px;
}
}
const
barOptionsData
=
reactive
<
EChartsOption
>
(
barOptions
)
as
EChartsOption
// 周活跃量
h3 {
const
getWeeklyUserActivity
=
async
()
=>
{
margin-top: 30px;
const
data
=
[
}
{
value
:
13253
,
name
:
'
analysis.monday
'
},
.top {
{
value
:
34235
,
name
:
'
analysis.tuesday
'
},
display: flex;
{
value
:
26321
,
name
:
'
analysis.wednesday
'
},
> div {
{
value
:
12340
,
name
:
'
analysis.thursday
'
},
padding: 20px;
{
value
:
24643
,
name
:
'
analysis.friday
'
},
margin: 10px;
{
value
:
1322
,
name
:
'
analysis.saturday
'
},
border: 1px dashed #999;
{
value
:
1324
,
name
:
'
analysis.sunday
'
}
]
set
(
barOptionsData
,
'
xAxis.data
'
,
data
.
map
((
v
)
=>
t
(
v
.
name
))
)
set
(
barOptionsData
,
'
series
'
,
[
{
name
:
t
(
'
analysis.activeQuantity
'
),
data
:
data
.
map
((
v
)
=>
v
.
value
),
type
:
'
bar
'
}
}
])
}
}
.top_1 {
const
getAllApi
=
async
()
=>
{
> div {
await
Promise
.
all
([
flex: 1;
getCount
(),
}
getProject
(),
getNotice
(),
getShortcut
(),
getUserAccessSource
(),
getWeeklyUserActivity
()
])
loading
.
value
=
false
}
}
.top_2 {
const
handleProjectClick
=
(
message
:
string
)
=>
{
> div {
window
.
open
(
`https://
${
message
}
`
,
'
_blank
'
)
flex: 1;
}
}
}
.top_3 {
const
handleShortcutClick
=
(
url
:
string
)
=>
{
justify-content: space-between;
router
.
push
(
url
)
flex-wrap: wrap;
> div {
width: 32%;
}
}
}
</
style
>
getAllApi
()
\ No newline at end of file
</
script
>
yudao-ui/yudao-ui-admin-vue3/src/views/Home/YearRangeSelector.vue
0 → 100644
View file @
6a3f72ad
<
template
>
<div
class=
"year-range-picker"
>
<el-input
v-model=
"displayValue"
readonly
clearable
@
click=
"toggleYearPanel"
@
clear=
"clearRange"
placeholder=
"选择年份范围"
class=
"year-input"
>
<template
#suffix
>
<i
class=
"el-icon-arrow-down"
:class=
"
{ 'is-reverse': panelVisible }">
</i>
</
template
>
</el-input>
<transition
name=
"fade"
>
<div
class=
"year-panel"
v-show=
"panelVisible"
>
<div
class=
"panel-header"
>
<div
class=
"year-type"
>
<el-radio-group
v-model=
"selectedType"
>
<el-radio-button
label=
"start"
>
开始年份
</el-radio-button>
<el-radio-button
label=
"end"
>
结束年份
</el-radio-button>
</el-radio-group>
</div>
<div
class=
"panel-actions"
>
<el-button
size=
"mini"
@
click=
"closePanel"
>
取消
</el-button>
<el-button
size=
"mini"
type=
"primary"
@
click=
"confirmRange"
:disabled=
"!isValidRange"
>
确定
</el-button
>
</div>
</div>
<div
class=
"year-grid"
>
<div
v-for=
"year in yearList"
:key=
"year"
:class=
"{
'year-item': true,
'is-selected': isSelectedYear(year),
'is-disabled': isDisabledYear(year)
}"
@
click=
"selectYear(year)"
>
{{ year }}年
</div>
</div>
<div
class=
"quick-options"
>
<el-button
size=
"small"
@
click=
"selectLastYear"
>
近1年
</el-button>
<el-button
size=
"small"
@
click=
"selectLast3Years"
>
近3年
</el-button>
<el-button
size=
"small"
@
click=
"selectLast5Years"
>
近5年
</el-button>
</div>
</div>
</transition>
</div>
</template>
<
script
lang=
"ts"
setup
>
import
{
ref
,
computed
,
watch
,
defineProps
,
defineEmits
,
nextTick
}
from
'
vue
'
import
{
ElInput
,
ElRadioGroup
,
ElRadioButton
,
ElButton
}
from
'
element-plus
'
const
props
=
defineProps
<
{
modelValue
:
[
string
,
string
]
|
[]
showQuickOptions
:
boolean
yearRange
:
[
number
,
number
]
// 可选年份范围 [开始年份, 结束年份]
}
>
()
const
emits
=
defineEmits
<
{
(
event
:
'
update:modelValue
'
,
value
:
[
string
,
string
]):
void
(
event
:
'
change
'
,
value
:
[
string
,
string
]):
void
}
>
()
// 内部状态
const
panelVisible
=
ref
(
false
)
const
selectedType
=
ref
(
'
start
'
)
// 当前选择的是开始还是结束年份
const
startYear
=
ref
<
string
|
null
>
(
props
.
modelValue
[
0
]
||
null
)
const
endYear
=
ref
<
string
|
null
>
(
props
.
modelValue
[
1
]
||
null
)
// 生成年份列表(默认可选范围:当前年份前后10年)
const
yearRange
=
computed
(()
=>
{
const
[
min
,
max
]
=
props
.
yearRange
||
[]
const
currentYear
=
new
Date
().
getFullYear
()
const
defaultMin
=
currentYear
-
10
const
defaultMax
=
currentYear
+
10
return
[
min
||
defaultMin
,
max
||
defaultMax
]
})
const
yearList
=
computed
(()
=>
{
const
[
min
,
max
]
=
yearRange
.
value
return
Array
.
from
({
length
:
max
-
min
+
1
},
(
_
,
i
)
=>
min
+
i
)
})
// 显示值
const
displayValue
=
computed
(()
=>
{
if
(
startYear
.
value
&&
endYear
.
value
)
{
return
`
${
startYear
.
value
}
年 -
${
endYear
.
value
}
年`
}
else
if
(
startYear
.
value
)
{
return
`
${
startYear
.
value
}
年 - 未选择结束年份`
}
else
if
(
endYear
.
value
)
{
return
`未选择开始年份 -
${
endYear
.
value
}
年`
}
return
''
})
// 判断年份是否已选择
const
isSelectedYear
=
(
year
:
number
)
=>
{
if
(
selectedType
.
value
===
'
start
'
)
{
return
year
.
toString
()
===
startYear
.
value
}
else
{
return
year
.
toString
()
===
endYear
.
value
}
}
// 判断年份是否禁用
const
isDisabledYear
=
(
year
:
number
)
=>
{
if
(
selectedType
.
value
===
'
start
'
&&
endYear
.
value
)
{
return
year
>
parseInt
(
endYear
.
value
)
}
else
if
(
selectedType
.
value
===
'
end
'
&&
startYear
.
value
)
{
return
year
<
parseInt
(
startYear
.
value
)
}
return
false
}
// 判断范围是否有效
const
isValidRange
=
computed
(()
=>
{
return
!!
(
startYear
.
value
&&
endYear
.
value
&&
startYear
.
value
<=
endYear
.
value
)
})
// 监听外部值变化
watch
(
()
=>
props
.
modelValue
,
(
newVal
)
=>
{
startYear
.
value
=
newVal
[
0
]
||
null
endYear
.
value
=
newVal
[
1
]
||
null
}
)
// 监听内部值变化,同步到外部
watch
([
startYear
,
endYear
],
([
newStart
,
newEnd
])
=>
{
if
(
newStart
&&
newEnd
&&
newStart
<=
newEnd
)
{
emits
(
'
update:modelValue
'
,
[
newStart
,
newEnd
])
}
})
// 切换年份面板显示
const
toggleYearPanel
=
()
=>
{
panelVisible
.
value
=
!
panelVisible
.
value
}
// 关闭面板
const
closePanel
=
()
=>
{
panelVisible
.
value
=
false
}
// 确认选择
const
confirmRange
=
()
=>
{
if
(
isValidRange
.
value
)
{
emits
(
'
update:modelValue
'
,
[
startYear
.
value
!
,
endYear
.
value
!
])
emits
(
'
change
'
,
[
startYear
.
value
!
,
endYear
.
value
!
])
panelVisible
.
value
=
false
}
}
// 选择年份
const
selectYear
=
(
year
:
number
)
=>
{
if
(
selectedType
.
value
===
'
start
'
)
{
startYear
.
value
=
year
.
toString
()
// 如果开始年份大于结束年份,清空结束年份
if
(
endYear
.
value
&&
startYear
.
value
>
endYear
.
value
)
{
endYear
.
value
=
null
}
}
else
{
endYear
.
value
=
year
.
toString
()
}
}
// 清空选择
const
clearRange
=
()
=>
{
startYear
.
value
=
null
endYear
.
value
=
null
emits
(
'
update:modelValue
'
,
[])
}
// 快捷选择:近1年
const
selectLastYear
=
()
=>
{
const
currentYear
=
new
Date
().
getFullYear
().
toString
()
const
lastYear
=
(
new
Date
().
getFullYear
()
-
1
).
toString
()
startYear
.
value
=
lastYear
endYear
.
value
=
currentYear
confirmRange
()
}
// 快捷选择:近3年
const
selectLast3Years
=
()
=>
{
const
currentYear
=
new
Date
().
getFullYear
().
toString
()
const
threeYearsAgo
=
(
new
Date
().
getFullYear
()
-
3
).
toString
()
startYear
.
value
=
threeYearsAgo
endYear
.
value
=
currentYear
confirmRange
()
}
// 快捷选择:近5年
const
selectLast5Years
=
()
=>
{
const
currentYear
=
new
Date
().
getFullYear
().
toString
()
const
fiveYearsAgo
=
(
new
Date
().
getFullYear
()
-
5
).
toString
()
startYear
.
value
=
fiveYearsAgo
endYear
.
value
=
currentYear
confirmRange
()
}
// 点击外部关闭面板
const
clickOutside
=
(
e
:
MouseEvent
)
=>
{
const
target
=
e
.
target
as
HTMLElement
const
pickerEl
=
document
.
querySelector
(
'
.year-range-picker
'
)
as
HTMLElement
if
(
pickerEl
&&
!
pickerEl
.
contains
(
target
))
{
panelVisible
.
value
=
false
}
}
// 添加点击外部事件监听
onMounted
(()
=>
{
document
.
addEventListener
(
'
click
'
,
clickOutside
)
})
// 移除事件监听
onUnmounted
(()
=>
{
document
.
removeEventListener
(
'
click
'
,
clickOutside
)
})
</
script
>
<
style
scoped
lang=
"less"
>
.year-range-picker {
position: relative;
.year-input {
cursor: pointer;
}
.year-panel {
position: absolute;
top: 40px;
left: 0;
width: 300px;
background: #000;
border: 1px solid #dcdcdc;
border-radius: 4px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
z-index: 1000;
padding: 10px;
.panel-header {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
.year-type {
.el-radio-button__inner {
padding: 6px 15px;
}
}
.panel-actions {
display: flex;
gap: 10px;
}
}
.year-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
margin-bottom: 15px;
.year-item {
padding: 8px;
text-align: center;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
&:hover:not(.is-disabled) {
background-color: #f5f7fa;
}
&.is-selected {
background-color: #409eff;
color: #fff;
}
&.is-disabled {
color: #c0c4cc;
cursor: not-allowed;
}
}
}
.quick-options {
display: flex;
gap: 10px;
margin-top: 10px;
}
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
}
</
style
>
\ No newline at end of file
yudao-ui/yudao-ui-admin-vue3/src/views/Home/card.vue
0 → 100644
View file @
6a3f72ad
<
template
>
<div
class=
"stats-container"
>
<div
class=
"stat-card"
>
<div
class=
"stat-item single"
>
<span
class=
"stat-number"
>
{{
props
.
homeInfoFirst
?.
bfkhsl
||
0
}}
</span>
<span
class=
"stat-label"
>
拜访客户数量
</span>
</div>
</div>
<div
class=
"stat-card"
>
<div
class=
"stat-item"
>
<span
class=
"stat-number"
>
{{
props
.
homeInfoFirst
?.
sykhsl
||
0
}}
</span>
<span
class=
"stat-label"
>
商业客户
</span>
</div>
<div
class=
"stat-item"
>
<span
class=
"stat-number"
>
{{
props
.
homeInfoFirst
?.
ylkhsl
||
0
}}
</span>
<span
class=
"stat-label"
>
医疗客户数量
</span>
</div>
</div>
<div
class=
"stat-card"
>
<div
class=
"stat-item single"
>
<span
class=
"stat-number"
>
{{
props
.
homeInfoFirst
?.
visitCount
||
0
}}
</span>
<span
class=
"stat-label"
>
客户拜访总次数
</span>
</div>
</div>
<div
class=
"stat-card"
>
<div
class=
"stat-item"
>
<span
class=
"stat-number"
>
{{
props
.
homeInfoFirst
?.
sykfcs
||
0
}}
</span>
<span
class=
"stat-label"
>
商业拜访次数
</span>
</div>
<div
class=
"stat-item"
>
<span
class=
"stat-number"
>
{{
props
.
homeInfoFirst
?.
ylkfcs
||
0
}}
</span>
<span
class=
"stat-label"
>
日常拜访次数
</span>
</div>
</div>
<div
class=
"stat-card"
>
<div
class=
"stat-item single"
>
<span
class=
"stat-number"
>
{{
props
.
homeInfoFirst
?.
ywysl
||
0
}}
</span>
<span
class=
"stat-label"
>
业务员数量
</span>
</div>
</div>
<!--
<div
class=
"stat-card"
>
<div
class=
"stat-item single"
>
<span
class=
"stat-number"
>
{{
statsData
.
card2
.
number
}}
</span>
<span
class=
"stat-label"
>
{{
statsData
.
card2
.
label
}}
</span>
</div>
</div>
-->
</div>
</
template
>
<
script
setup
lang=
"ts"
>
const
props
=
defineProps
({
homeInfoFirst
:
{
type
:
[
Object
,
String
],
// eslint-disable-next-line vue/require-valid-default-prop
default
:
{}
}
})
onMounted
(()
=>
{})
</
script
>
<
style
scoped
lang=
"css"
>
.stats-container
{
display
:
flex
;
justify-content
:
space-around
;
padding
:
20px
100px
;
gap
:
20px
;
/* 卡片之间的间距 */
flex-wrap
:
wrap
;
/* 适配小屏幕,自动换行 */
}
.stat-card
{
border
:
1px
solid
#ccc
;
border-radius
:
4px
;
padding
:
16px
;
min-width
:
200px
;
background-color
:
#fff
;
box-shadow
:
0
2px
4px
rgba
(
0
,
0
,
0
,
0.1
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
.stat-item
{
text-align
:
center
;
margin
:
0
10px
;
}
.stat-item.single
{
margin
:
0
;
}
.stat-number
{
font-size
:
24px
;
font-weight
:
bold
;
display
:
block
;
color
:
#000
;
}
.stat-label
{
font-size
:
14px
;
color
:
#666
;
}
</
style
>
\ No newline at end of file
yudao-ui/yudao-ui-admin-vue3/src/views/Home/components/BarChart.vue
0 → 100644
View file @
6a3f72ad
<
template
>
<div
class=
"bar-chart"
>
<h3
class=
"chart-title"
>
{{
title
}}
</h3>
<chartsCard
:option=
"chartOption"
:width=
"width"
:height=
"height"
@
chart-ready=
"handleChartReady"
/>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
computed
}
from
'
vue
'
import
chartsCard
from
'
./chartsCard.vue
'
const
props
=
defineProps
({
title
:
{
type
:
String
,
default
:
'
柱状图
'
},
xData
:
{
type
:
Array
,
required
:
true
// X轴数据(通常是类别)
},
yData
:
{
type
:
Array
,
required
:
true
// Y轴数据(数值)
},
seriesName
:
{
type
:
String
,
default
:
'
数据
'
// 系列名称
},
width
:
{
type
:
String
,
default
:
'
100%
'
},
height
:
{
type
:
String
,
default
:
'
400px
'
},
color
:
{
type
:
String
,
default
:
'
#5793f3
'
// 柱子颜色
},
barWidth
:
{
type
:
[
String
,
Number
],
default
:
'
40%
'
// 柱子宽度
}
})
const
chartOption
=
computed
(()
=>
({
tooltip
:
{
trigger
:
'
axis
'
,
backgroundColor
:
'
rgba(255, 255, 255, 0.9)
'
,
borderColor
:
'
#ddd
'
,
borderWidth
:
1
,
textStyle
:
{
color
:
'
#333
'
},
formatter
:
(
params
:
any
)
=>
{
const
p
=
params
[
0
]
return
`
${
p
.
name
}
<br/>
${
p
.
seriesName
}
:
${
p
.
value
}
`
}
},
grid
:
{
left
:
'
3%
'
,
right
:
'
4%
'
,
bottom
:
'
3%
'
,
containLabel
:
true
},
xAxis
:
{
type
:
'
category
'
,
data
:
props
.
xData
,
axisTick
:
{
show
:
false
},
axisLine
:
{
lineStyle
:
{
color
:
'
#999
'
}
},
axisLabel
:
{
color
:
'
#666
'
,
interval
:
0
,
// 强制显示所有标签
rotate
:
45
// 标签旋转角度
}
},
yAxis
:
{
type
:
'
value
'
,
axisTick
:
{
show
:
false
},
axisLine
:
{
show
:
false
},
axisLabel
:
{
color
:
'
#666
'
},
splitLine
:
{
lineStyle
:
{
color
:
'
#eee
'
}
}
},
series
:
[
{
name
:
props
.
seriesName
,
type
:
'
bar
'
,
data
:
props
.
yData
,
barWidth
:
props
.
barWidth
,
itemStyle
:
{
color
:
props
.
color
,
borderRadius
:
[
4
,
4
,
0
,
0
]
// 柱子圆角
},
emphasis
:
{
itemStyle
:
{
color
:
`
${
props
.
color
}
DD`
// 高亮颜色
}
}
}
]
}))
const
handleChartReady
=
(
chart
:
any
)
=>
{
// 可添加交互逻辑
}
</
script
>
<
style
scoped
lang=
"less"
>
.bar-chart {
.chart-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 10px;
color: #333;
}
}
</
style
>
\ No newline at end of file
yudao-ui/yudao-ui-admin-vue3/src/views/Home/components/LineChart.vue
0 → 100644
View file @
6a3f72ad
<
template
>
<div
class=
"line-chart"
>
<h3
class=
"chart-title"
>
{{
title
}}
</h3>
<chartsCard
:option=
"chartOption"
:width=
"width"
:height=
"height"
@
chart-ready=
"handleChartReady"
/>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
computed
}
from
'
vue
'
import
chartsCard
from
'
./chartsCard.vue
'
const
props
=
defineProps
({
title
:
{
type
:
String
,
default
:
'
折线图
'
},
xData
:
{
type
:
Array
,
required
:
true
// X轴数据(通常是时间或类别)
},
yData
:
{
type
:
Array
,
required
:
true
// Y轴数据(数值)
},
seriesName
:
{
type
:
String
,
default
:
'
数据
'
// 系列名称
},
width
:
{
type
:
String
,
default
:
'
100%
'
},
height
:
{
type
:
String
,
default
:
'
400px
'
},
color
:
{
type
:
String
,
default
:
'
#5793f3
'
// 线条颜色
},
isSmooth
:
{
type
:
Boolean
,
default
:
true
// 是否平滑曲线
}
})
const
chartOption
=
computed
(()
=>
({
tooltip
:
{
trigger
:
'
axis
'
,
backgroundColor
:
'
rgba(255, 255, 255, 0.9)
'
,
borderColor
:
'
#ddd
'
,
borderWidth
:
1
,
textStyle
:
{
color
:
'
#333
'
},
formatter
:
(
params
:
any
)
=>
{
const
p
=
params
[
0
]
return
`
${
p
.
name
}
<br/>
${
p
.
seriesName
}
:
${
p
.
value
}
`
}
},
grid
:
{
left
:
'
3%
'
,
right
:
'
4%
'
,
bottom
:
'
3%
'
,
containLabel
:
true
},
xAxis
:
{
type
:
'
category
'
,
data
:
props
.
xData
,
axisTick
:
{
show
:
false
},
axisLine
:
{
lineStyle
:
{
color
:
'
#999
'
}
},
axisLabel
:
{
color
:
'
#666
'
}
},
yAxis
:
{
type
:
'
value
'
,
axisTick
:
{
show
:
false
},
axisLine
:
{
show
:
false
},
axisLabel
:
{
color
:
'
#666
'
},
splitLine
:
{
lineStyle
:
{
color
:
'
#eee
'
}
}
},
series
:
[
{
name
:
props
.
seriesName
,
type
:
'
line
'
,
data
:
props
.
yData
,
smooth
:
props
.
isSmooth
,
lineStyle
:
{
width
:
2
,
color
:
props
.
color
},
itemStyle
:
{
color
:
props
.
color
,
borderWidth
:
2
},
symbol
:
'
circle
'
,
// 标记点形状
symbolSize
:
6
,
// 标记点大小
showSymbol
:
false
,
// 默认不显示标记点,鼠标悬停时显示
emphasis
:
{
symbol
:
'
circle
'
,
symbolSize
:
8
},
areaStyle
:
{
color
:
{
type
:
'
linear
'
,
x
:
0
,
y
:
0
,
x2
:
0
,
y2
:
1
,
colorStops
:
[
{
offset
:
0
,
color
:
`
${
props
.
color
}
80`
},
// 半透明
{
offset
:
1
,
color
:
`
${
props
.
color
}
10`
}
// 几乎透明
]
}
}
}
]
}))
const
handleChartReady
=
(
chart
:
any
)
=>
{
// 可添加交互逻辑
}
</
script
>
<
style
scoped
lang=
"less"
>
.line-chart {
.chart-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 10px;
color: #333;
}
}
</
style
>
\ No newline at end of file
yudao-ui/yudao-ui-admin-vue3/src/views/Home/components/PieChart.vue
0 → 100644
View file @
6a3f72ad
<
template
>
<div
class=
"visit-way-pie-chart"
>
<h3
class=
"chart-title"
>
客户拜访方式占比情况
</h3>
<chartsCard
:option=
"chartOption"
:width=
"width"
:height=
"height"
@
chart-ready=
"handleChartReady"
/>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
ref
,
computed
}
from
'
vue
'
import
chartsCard
from
'
./chartsCard.vue
'
const
props
=
defineProps
({
data
:
{
type
:
Array
,
required
:
true
// 数据格式示例:
// [
// { name: '电话', value: 765, color: '#5793f3' },
// { name: '上门', value: 560, color: '#a9d672' },
// { name: '社交网络', value: 400, color: '#fac858' },
// { name: '推广渠道', value: 200, color: '#f96c6a' },
// { name: '其他', value: 120, color: '#c2a6f2' },
// ]
},
total
:
{
type
:
Number
,
required
:
true
// 总拜访量
},
width
:
{
type
:
String
,
default
:
'
400px
'
},
height
:
{
type
:
String
,
default
:
'
400px
'
},
tooltipTip
:
{
type
:
String
,
default
:
'
拜访方式支持自定义类型添加
'
// 提示框内容
}
})
const
chartOption
=
computed
(()
=>
{
// console.log(props.data,"props.data");
const
legendData
=
props
.
data
.
map
((
item
)
=>
item
.
name
)
return
{
tooltip
:
{
trigger
:
'
item
'
,
formatter
:
'
{a} <br/>{b} : {c} ({d}%)
'
,
// 自定义提示框浮层样式
extraCssText
:
'
background: #fff9cc; border: 1px solid #ffe57f; padding: 8px;
'
,
// 提示框位置
position
:
(
point
)
=>
{
const
x
=
point
[
0
]
>
300
?
point
[
0
]
-
100
:
point
[
0
]
const
y
=
point
[
1
]
<
100
?
point
[
1
]
+
20
:
point
[
1
]
return
[
x
,
y
]
},
// 提示框内容追加说明
formatter
:
(
params
)
=>
{
const
baseInfo
=
`
${
params
.
seriesName
}
<br/>
${
params
.
marker
}
${
params
.
name
}
:
${
params
.
value
}
(
${
params
.
percent
}
%)`
return
`
${
baseInfo
}
<br/><span style="color: #999; font-size: 12px;">
${
props
.
tooltipTip
}
</span>`
}
},
legend
:
{
orient
:
'
vertical
'
,
right
:
10
,
top
:
'
center
'
,
align
:
'
left
'
,
icon
:
'
circle
'
,
// 图例标记为圆形
itemWidth
:
12
,
itemHeight
:
12
,
itemGap
:
15
,
textStyle
:
{
color
:
'
#333
'
,
fontSize
:
14
},
data
:
legendData
},
series
:
[
{
name
:
'
客户拜访方式占比情况
'
,
type
:
'
pie
'
,
radius
:
[
'
40%
'
,
'
70%
'
],
// 环形图内外半径
center
:
[
'
35%
'
,
'
50%
'
],
// 图表中心位置,留出右侧图例空间
label
:
{
show
:
false
// 隐藏饼图扇区默认标签
},
labelLine
:
{
show
:
false
// 隐藏标签连线
},
data
:
props
.
data
.
map
((
item
)
=>
({
name
:
item
.
name
,
value
:
item
.
value
,
itemStyle
:
{
color
:
item
.
color
}
})),
// 中心文字配置
emphasis
:
{
label
:
{
show
:
false
}
},
// 中心显示总拜访量
renderItem
:
(
params
,
api
)
=>
{
if
(
params
.
name
===
''
)
{
return
{
type
:
'
text
'
,
position
:
api
.
coord
([
api
.
value
(
0
),
api
.
value
(
1
)]),
content
:
`总拜访量\n
${
props
.
total
}
`
,
style
:
{
textAlign
:
'
center
'
,
fill
:
'
#333
'
,
fontSize
:
16
,
fontWeight
:
'
bold
'
,
lineHeight
:
1.4
}
}
}
return
null
}
}
]
}
})
const
handleChartReady
=
(
chart
:
echarts
.
ECharts
)
=>
{
// 可在此处对图表进行更多自定义操作,如监听事件等
}
</
script
>
<
style
scoped
lang=
"less"
>
.visit-way-pie-chart {
.chart-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 10px;
color: #333;
}
}
</
style
>
\ No newline at end of file
yudao-ui/yudao-ui-admin-vue3/src/views/Home/components/barChart_1.vue
0 → 100644
View file @
6a3f72ad
<
template
>
<div
class=
"horizontal-bar-chart"
>
<h3
class=
"chart-title"
>
{{
title
}}
</h3>
<chartsCard
:option=
"chartOption"
:width=
"width"
:height=
"height"
@
chart-ready=
"handleChartReady"
/>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
computed
}
from
'
vue
'
import
chartsCard
from
'
./chartsCard.vue
'
const
props
=
defineProps
({
title
:
{
type
:
String
,
default
:
'
拜访人均分布情况
'
},
data
:
{
type
:
Array
,
required
:
true
// 数据格式示例:
// [
// { name: '业务员1', value: 78, color: '#fcd36e' },
// { name: '业务员2', value: 68, color: '#f28b8b' },
// { name: '业务员3', value: 50, color: '#a491f2' },
// { name: '业务员4', value: 42, color: '#fbbc58' },
// { name: '业务员5', value: 32, color: '#7ecb5c' },
// { name: '业务员6', value: 30, color: '#68c0cf' },
// { name: '业务员7', value: 28, color: '#66b1fc' },
// ]
},
width
:
{
type
:
String
,
default
:
'
100%
'
},
height
:
{
type
:
String
,
default
:
'
400px
'
}
})
const
colors
=
[
'
#fcd36e
'
,
'
#f28b8b
'
,
'
#a491f2
'
,
'
#fbbc58
'
,
'
#7ecb5c
'
,
'
#68c0cf
'
,
'
#66b1fc
'
]
const
chartOption
=
computed
(()
=>
({
tooltip
:
{
trigger
:
'
item
'
,
formatter
:
'
{b} : {c}
'
// 格式化提示内容,显示名称和数值
},
grid
:
{
left
:
'
60px
'
,
// 左侧留出空间显示业务员名称
right
:
'
40px
'
,
bottom
:
'
3%
'
,
containLabel
:
true
},
xAxis
:
{
type
:
'
value
'
,
// min: 0, // 最小值
// max: 100000, // 最大值
axisTick
:
{
show
:
false
},
axisLine
:
{
show
:
false
},
axisLabel
:
{
color
:
'
#666
'
},
splitLine
:
{
lineStyle
:
{
color
:
'
#eee
'
}
}
},
yAxis
:
{
type
:
'
category
'
,
data
:
props
.
data
.
map
((
item
)
=>
item
.
name
),
axisTick
:
{
show
:
false
},
axisLine
:
{
show
:
false
},
axisLabel
:
{
color
:
'
#333
'
,
fontSize
:
14
}
},
series
:
[
{
name
:
'
拜访人均分布
'
,
type
:
'
bar
'
,
barCategoryGap
:
'
10%
'
,
// 柱子之间的间距
barWidth
:
20
,
// 柱子宽度
data
:
props
.
data
.
map
((
item
)
=>
item
.
value
),
itemStyle
:
{
color
:
(
params
:
{
dataIndex
:
number
})
=>
{
const
item
=
props
.
data
[
params
.
dataIndex
]
return
item
.
color
||
colors
[
params
.
dataIndex
%
colors
.
length
]
},
borderRadius
:
[
0
,
6
,
6
,
0
]
// 仅右侧圆角,适配横向柱状图
},
label
:
{
show
:
true
,
position
:
'
right
'
,
// 数值显示在柱子右侧
color
:
'
#333
'
,
fontSize
:
12
}
}
]
}))
const
handleChartReady
=
(
chart
:
any
)
=>
{
// 可在此处对图表进行更多自定义操作,如监听事件等
}
</
script
>
<
style
scoped
lang=
"less"
>
.horizontal-bar-chart {
.chart-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 10px;
color: '#333';
}
}
</
style
>
\ No newline at end of file
yudao-ui/yudao-ui-admin-vue3/src/views/Home/components/chartsCard.vue
0 → 100644
View file @
6a3f72ad
<
template
>
<div
ref=
"chartRef"
class=
"echarts-base"
:style=
"
{ width, height }">
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
ref
,
onMounted
,
onUnmounted
,
watch
,
nextTick
}
from
'
vue
'
import
*
as
echarts
from
'
echarts
'
const
props
=
defineProps
({
option
:
{
type
:
Object
,
required
:
true
},
width
:
{
type
:
String
,
default
:
'
100%
'
},
height
:
{
type
:
String
,
default
:
'
400px
'
}
})
const
emits
=
defineEmits
([
'
chartReady
'
,
'
chartClick
'
])
const
chartRef
=
ref
<
HTMLElement
|
null
>
(
null
)
let
myChart
:
echarts
.
ECharts
|
null
=
null
const
initChart
=
()
=>
{
nextTick
(()
=>
{
if
(
chartRef
.
value
)
{
myChart
=
echarts
.
init
(
chartRef
.
value
)
myChart
.
setOption
(
props
.
option
)
emits
(
'
chartReady
'
,
myChart
)
myChart
.
on
(
'
click
'
,
(
params
)
=>
{
emits
(
'
chartClick
'
,
params
)
})
}
})
}
const
resizeChart
=
()
=>
{
myChart
?.
resize
()
}
onMounted
(()
=>
{
initChart
()
window
.
addEventListener
(
'
resize
'
,
resizeChart
)
})
onUnmounted
(()
=>
{
window
.
removeEventListener
(
'
resize
'
,
resizeChart
)
myChart
?.
dispose
()
myChart
=
null
})
watch
(
()
=>
props
.
option
,
()
=>
{
if
(
myChart
)
{
myChart
.
setOption
(
props
.
option
)
}
},
{
deep
:
true
}
)
</
script
>
<
style
scoped
>
.echarts-base
{
width
:
100%
;
height
:
100%
;
}
</
style
>
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment