Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
shun peng
/
lufa-ai
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
60dd1279
authored
Apr 03, 2026
by
pengshun
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 添加公租房和长租房的区分
parent
da7791dc
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
108 additions
and
48 deletions
+108
-48
.DS_Store
+0
-0
ruoyi-vue-pro-master-jdk17/.DS_Store
+0
-0
ruoyi-vue-pro-master-jdk17/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/analysis/AiChatAnalysisRespVO.java
+3
-0
ruoyi-vue-pro-master-jdk17/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/analysis/AiChatAnalysisStatisticsReqVO.java
+3
-0
ruoyi-vue-pro-master-jdk17/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/analysis/AiChatAnalysysCreateMyReqVO.java
+3
-0
ruoyi-vue-pro-master-jdk17/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/analysis/AiChatKeywordsPageReqVO.java
+3
-0
ruoyi-vue-pro-master-jdk17/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatAnalysisDO.java
+2
-0
ruoyi-vue-pro-master-jdk17/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatAnalysisMapper.java
+9
-4
ruoyi-vue-pro-master-jdk17/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatAnalysisServiceImpl.java
+4
-4
ruoyi-vue-pro-master-jdk17/yudao-server/.DS_Store
+0
-0
yudao-ui-admin-vue3/.DS_Store
+0
-0
yudao-ui-admin-vue3/src/layout/components/Footer/src/Footer.vue
+1
-1
yudao-ui-admin-vue3/src/views/ai/chat_analysis/analysis/index.vue
+80
-39
No files found.
.DS_Store
View file @
60dd1279
No preview for this file type
ruoyi-vue-pro-master-jdk17/.DS_Store
View file @
60dd1279
No preview for this file type
ruoyi-vue-pro-master-jdk17/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/analysis/AiChatAnalysisRespVO.java
View file @
60dd1279
...
...
@@ -49,4 +49,7 @@ public class AiChatAnalysisRespVO implements VO {
@Schema
(
description
=
"是否转人工"
)
private
Boolean
isTransferHuman
;
@Schema
(
description
=
"是否公租房"
)
private
Boolean
isPublicRental
;
}
ruoyi-vue-pro-master-jdk17/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/analysis/AiChatAnalysisStatisticsReqVO.java
View file @
60dd1279
...
...
@@ -20,4 +20,7 @@ public class AiChatAnalysisStatisticsReqVO extends PageParam {
@DateTimeFormat
(
pattern
=
FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND
)
private
LocalDateTime
[]
chatTime
;
@Schema
(
description
=
"是否公租房"
)
private
Boolean
isPublicRental
;
}
ruoyi-vue-pro-master-jdk17/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/analysis/AiChatAnalysysCreateMyReqVO.java
View file @
60dd1279
...
...
@@ -58,4 +58,7 @@ public class AiChatAnalysysCreateMyReqVO {
@Schema
(
description
=
"是否转人工"
)
private
Boolean
isTransferHuman
;
@Schema
(
description
=
"是否公租房"
)
private
Boolean
isPublicRental
;
}
ruoyi-vue-pro-master-jdk17/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/analysis/AiChatKeywordsPageReqVO.java
View file @
60dd1279
...
...
@@ -17,4 +17,7 @@ public class AiChatKeywordsPageReqVO extends PageParam {
@DateTimeFormat
(
pattern
=
FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND
)
private
LocalDateTime
[]
chatTime
;
@Schema
(
description
=
"是否公租房"
)
private
Boolean
isPublicRental
;
}
ruoyi-vue-pro-master-jdk17/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatAnalysisDO.java
View file @
60dd1279
...
...
@@ -84,4 +84,6 @@ public class AiChatAnalysisDO extends BaseDO {
private
Boolean
isTransferHuman
;
private
Boolean
isPublicRental
;
}
ruoyi-vue-pro-master-jdk17/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/mysql/chat/AiChatAnalysisMapper.java
View file @
60dd1279
...
...
@@ -60,18 +60,22 @@ public interface AiChatAnalysisMapper extends BaseMapperX<AiChatAnalysisDO> {
/**
* 统计指定时间范围内的问答总数
*/
default
Long
selectCountByTimeRange
(
LocalDateTime
[]
timeRange
)
{
default
Long
selectCountByTimeRange
(
LocalDateTime
[]
timeRange
,
Boolean
isPublic
)
{
return
selectCount
(
new
LambdaQueryWrapperX
<
AiChatAnalysisDO
>()
.
betweenIfPresent
(
AiChatAnalysisDO:
:
getChatTime
,
timeRange
));
.
betweenIfPresent
(
AiChatAnalysisDO:
:
getChatTime
,
timeRange
)
.
eqIfPresent
(
AiChatAnalysisDO:
:
getIsPublicRental
,
isPublic
)
);
}
/**
* 统计指定时间范围内的转人工总数
*/
default
Long
selectTransferCountByTimeRange
(
LocalDateTime
[]
timeRange
)
{
default
Long
selectTransferCountByTimeRange
(
LocalDateTime
[]
timeRange
,
Boolean
isPublic
)
{
return
selectCount
(
new
LambdaQueryWrapperX
<
AiChatAnalysisDO
>()
.
betweenIfPresent
(
AiChatAnalysisDO:
:
getChatTime
,
timeRange
)
.
eq
(
AiChatAnalysisDO:
:
getIsTransferHuman
,
true
));
.
eq
(
AiChatAnalysisDO:
:
getIsTransferHuman
,
true
)
.
eqIfPresent
(
AiChatAnalysisDO:
:
getIsPublicRental
,
isPublic
)
);
}
/**
...
...
@@ -81,6 +85,7 @@ public interface AiChatAnalysisMapper extends BaseMapperX<AiChatAnalysisDO> {
// 只查询 question_keywords 字段
return
selectObjs
(
new
LambdaQueryWrapperX
<
AiChatAnalysisDO
>()
.
betweenIfPresent
(
AiChatAnalysisDO:
:
getChatTime
,
reqVo
.
getChatTime
())
.
eqIfPresent
(
AiChatAnalysisDO:
:
getIsPublicRental
,
reqVo
.
getIsPublicRental
())
.
select
(
AiChatAnalysisDO:
:
getQuestionKeywords
));
}
}
ruoyi-vue-pro-master-jdk17/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatAnalysisServiceImpl.java
View file @
60dd1279
...
...
@@ -112,13 +112,13 @@ public class AiChatAnalysisServiceImpl implements AiChatAnalysisService {
// 1. 计算时间范围
// 2. 查询当前周期数据
Long
currentTotal
=
chatAnalysisMapper
.
selectCountByTimeRange
(
pageReqVO
.
getChatTime
());
Long
currentTransfer
=
chatAnalysisMapper
.
selectTransferCountByTimeRange
(
pageReqVO
.
getChatTime
());
Long
currentTotal
=
chatAnalysisMapper
.
selectCountByTimeRange
(
pageReqVO
.
getChatTime
()
,
pageReqVO
.
getIsPublicRental
()
);
Long
currentTransfer
=
chatAnalysisMapper
.
selectTransferCountByTimeRange
(
pageReqVO
.
getChatTime
()
,
pageReqVO
.
getIsPublicRental
()
);
LocalDateTime
[]
previousRange
=
computePreviousRange
(
pageReqVO
.
getChatTime
());
// 3. 查询上一周期数据
Long
previousTotal
=
chatAnalysisMapper
.
selectCountByTimeRange
(
previousRange
);
Long
previousTransfer
=
chatAnalysisMapper
.
selectTransferCountByTimeRange
(
previousRange
);
Long
previousTotal
=
chatAnalysisMapper
.
selectCountByTimeRange
(
previousRange
,
pageReqVO
.
getIsPublicRental
()
);
Long
previousTransfer
=
chatAnalysisMapper
.
selectTransferCountByTimeRange
(
previousRange
,
pageReqVO
.
getIsPublicRental
()
);
// 4. 计算百分比(与上周期对比)
Double
percentQAPrevious
=
calculatePercent
(
currentTotal
,
previousTotal
);
...
...
ruoyi-vue-pro-master-jdk17/yudao-server/.DS_Store
View file @
60dd1279
No preview for this file type
yudao-ui-admin-vue3/.DS_Store
View file @
60dd1279
No preview for this file type
yudao-ui-admin-vue3/src/layout/components/Footer/src/Footer.vue
View file @
60dd1279
...
...
@@ -19,6 +19,6 @@ const title = computed(() => appStore.getTitle)
:class=
"prefixCls"
class=
"h-[var(--app-footer-height)] bg-[var(--app-content-bg-color)] text-center leading-[var(--app-footer-height)] text-[var(--el-text-color-placeholder)] dark:bg-[var(--el-bg-color)] overflow-hidden"
>
<span
class=
"text-14px"
>
Copyright ©202
2
-
{{
title
}}
</span>
<span
class=
"text-14px"
>
Copyright ©202
6
-
{{
title
}}
</span>
</div>
</
template
>
yudao-ui-admin-vue3/src/views/ai/chat_analysis/analysis/index.vue
View file @
60dd1279
<
template
>
<ContentWrap>
<!-- 新增外层 Tabs:公租房 / 长租房 -->
<el-tabs
v-model=
"rentalType"
@
tab-change=
"handleRentalTabChange"
>
<el-tab-pane
label=
"公租房"
name=
"public"
/>
<el-tab-pane
label=
"长租房"
name=
"long"
/>
</el-tabs>
<!-- 原有周期 Tabs -->
<el-tabs
v-model=
"activeTab"
@
tab-change=
"handleTabChange"
>
<el-tab-pane
v-for=
"(config, key) in periodConfigs"
:key=
"key"
:label=
"config.label + '数据'"
<el-tab-pane
v-for=
"(config, key) in periodConfigs"
:key=
"key"
:label=
"config.label + '数据'"
:name=
"key"
>
<div
class=
"analysis-container p-20px min-h-500px"
>
<el-card
shadow=
"never"
class=
"period-card"
v-loading=
"loading"
>
<div
class=
"stats-container"
>
<div
class=
"stat-item"
>
<div
class=
"stat-label"
>
问答总数
</div>
<div
class=
"stat-main"
>
<span
class=
"num"
>
{{
statsData
?.
totalCount
||
0
}}
</span>
<span
:class=
"['growth', (statsData?.percentQAPrevious || 0) >= 0 ? 'up' : 'down']"
>
<span
:class=
"[
'growth',
(statsData?.percentQAPrevious || 0) >= 0 ? 'up' : 'down'
]"
>
{{
(
statsData
?.
percentQAPrevious
||
100
)
>=
0
?
'↑'
:
'↓'
}}
{{
Math
.
abs
(
statsData
?.
percentQAPrevious
||
100
).
toFixed
(
2
)
}}
%
</span>
...
...
@@ -29,7 +40,12 @@
<div
class=
"stat-label"
>
转人工总数
</div>
<div
class=
"stat-main"
>
<span
class=
"num"
>
{{
statsData
?.
transferCount
||
0
}}
</span>
<span
:class=
"['growth', (statsData?.percentTransPrevious || 0) >= 0 ? 'up' : 'down']"
>
<span
:class=
"[
'growth',
(statsData?.percentTransPrevious || 0) >= 0 ? 'up' : 'down'
]"
>
{{
(
statsData
?.
percentTransPrevious
||
0
)
>=
0
?
'↑'
:
'↓'
}}
{{
Math
.
abs
(
statsData
?.
percentTransPrevious
||
0
).
toFixed
(
2
)
}}
%
</span>
...
...
@@ -40,7 +56,7 @@
<div
class=
"word-section mt-30px"
>
<div
class=
"flex justify-between items-center mb-10px"
>
<span
class=
"text-16px font-bold"
>
高频问答
词
</span>
<span
class=
"text-16px font-bold"
>
高频问答
</span>
<el-button
link
type=
"primary"
@
click=
"isExpanded = !isExpanded"
>
{{
isExpanded
?
'收起列表'
:
'展开列表'
}}
<Icon
:icon=
"isExpanded ? 'ep:arrow-up' : 'ep:arrow-down'"
/>
...
...
@@ -49,11 +65,25 @@
<el-collapse-transition>
<div
v-show=
"isExpanded"
>
<el-table
:data=
"displayWords"
size=
"default"
stripe
v-loading=
"tableLoading"
>
<el-table-column
label=
"关键词/问题"
prop=
"keyword"
show-overflow-tooltip
/>
<el-table-column
label=
"出现频次"
prop=
"count"
width=
"120"
align=
"center"
/>
<el-table
:data=
"displayWords"
size=
"default"
stripe
v-loading=
"tableLoading"
>
<el-table-column
label=
""
prop=
"keyword"
show-overflow-tooltip
/>
<el-table-column
label=
"出现频次"
prop=
"count"
width=
"120"
align=
"center"
/>
</el-table>
<div
class=
"flex justify-center mt-20px"
>
<el-pagination
background
...
...
@@ -75,17 +105,16 @@
</
template
>
<
script
setup
lang=
"ts"
>
import
{
ref
,
reactive
,
computed
,
onMounted
}
from
'vue'
import
{
ref
,
computed
,
onMounted
}
from
'vue'
import
{
ChatAnalysisApi
}
from
'@/api/ai/chat/analysis'
import
dayjs
from
'dayjs'
import
isoWeek
from
'dayjs/plugin/isoWeek'
// 用于处理周一为一周开始
dayjs
.
extend
(
isoWeek
)
import
isoWeek
from
'dayjs/plugin/isoWeek'
dayjs
.
extend
(
isoWeek
)
/** AI 聊天对话 管理 */
defineOptions
({
name
:
'AiChatAnalysis'
})
//
默认选中“today”标签
//
原周期相关
const
activeTab
=
ref
(
'today'
)
const
loading
=
ref
(
false
)
const
tableLoading
=
ref
(
false
)
...
...
@@ -95,12 +124,11 @@ const isExpanded = ref(true)
const
currentPage
=
ref
(
1
)
const
pageSize
=
ref
(
10
)
const
totalWords
=
ref
(
0
)
const
wordList
=
ref
([])
const
statsData
=
ref
<
any
>
(
null
)
const
allWords
=
ref
<
any
[]
>
([])
const
allWords
=
ref
<
AiChatKeywordCountVO
[]
>
([])
// 存储全量关键词列表
const
statsData
=
ref
<
any
>
(
null
)
// 计算属性:当前页
要
显示的关键词
// 计算属性:当前页显示的关键词
const
displayWords
=
computed
(()
=>
{
const
start
=
(
currentPage
.
value
-
1
)
*
pageSize
.
value
const
end
=
start
+
pageSize
.
value
...
...
@@ -114,6 +142,14 @@ const periodConfigs: Record<string, any> = {
month
:
{
label
:
'本月'
,
compareUnit
:
'月'
,
tagType
:
'warning'
}
}
// ================= 新增:公租房 / 长租房 =================
const
rentalType
=
ref
<
'public'
|
'long'
>
(
'public'
)
// 默认公租房
/** 将 rentalType 转换为接口需要的 isPublicRental 布尔值 */
const
getIsPublicRental
=
():
boolean
=>
{
return
rentalType
.
value
===
'public'
// 公租房 => true,长租房 => false
}
/**
* 根据周期获取起止时间范围
* @param period today / week / month
...
...
@@ -126,18 +162,16 @@ const getTimeRangeByPeriod = (period: string): [string, string] => {
switch
(
period
)
{
case
'today'
:
// start = now.startOf('day') // 当天 00:00:00
// end = now.endOf('day') // 当天 23:59:59
start
=
now
.
subtract
(
1
,
'day'
).
startOf
(
'day'
)
end
=
now
.
subtract
(
1
,
'day'
).
endOf
(
'day'
)
break
case
'week'
:
start
=
now
.
startOf
(
'isoWeek'
)
// 本周一 00:00:00
end
=
now
.
endOf
(
'isoWeek'
)
// 本周日 23:59:59
start
=
now
.
startOf
(
'isoWeek'
)
end
=
now
.
endOf
(
'isoWeek'
)
break
case
'month'
:
start
=
now
.
startOf
(
'month'
)
// 本月第一天 00:00:00
end
=
now
.
endOf
(
'month'
)
// 本月最后一天 23:59:59
start
=
now
.
startOf
(
'month'
)
end
=
now
.
endOf
(
'month'
)
break
default
:
start
=
now
.
startOf
(
'day'
)
...
...
@@ -147,7 +181,7 @@ const getTimeRangeByPeriod = (period: string): [string, string] => {
return
[
start
.
format
(
'YYYY-MM-DD HH:mm:ss'
),
end
.
format
(
'YYYY-MM-DD HH:mm:ss'
)]
}
/** 统一加载数据方法 */
/** 统一加载数据方法
(含外层 rentalType 和内层周期)
*/
const
loadAllData
=
async
()
=>
{
await
Promise
.
all
([
fetchStats
(),
fetchWordList
()])
}
...
...
@@ -158,25 +192,26 @@ const fetchStats = async () => {
try
{
const
[
start
,
end
]
=
getTimeRangeByPeriod
(
activeTab
.
value
)
statsData
.
value
=
await
ChatAnalysisApi
.
getChatStatistics
({
chatTime
:
[
start
,
end
]
// 传入时间范围
chatTime
:
[
start
,
end
],
isPublicRental
:
getIsPublicRental
()
// 新增参数
})
}
finally
{
loading
.
value
=
false
}
}
/** 加载高频词
分页列表
*/
/** 加载高频词
全量列表(前端分页)
*/
const
fetchWordList
=
async
()
=>
{
tableLoading
.
value
=
true
try
{
const
[
start
,
end
]
=
getTimeRangeByPeriod
(
activeTab
.
value
)
const
res
=
await
ChatAnalysisApi
.
getChatKeywords
({
chatTime
:
[
start
,
end
]
chatTime
:
[
start
,
end
],
isPublicRental
:
getIsPublicRental
()
// 新增参数
})
allWords
.
value
=
res
||
[]
totalWords
.
value
=
allWords
.
value
.
length
// 如果当前页码超出总页数,重置为最后一页或第一页
const
maxPage
=
Math
.
ceil
(
totalWords
.
value
/
pageSize
.
value
)
||
1
if
(
currentPage
.
value
>
maxPage
)
{
currentPage
.
value
=
maxPage
...
...
@@ -186,16 +221,22 @@ const fetchWordList = async () => {
}
}
/**
切换 Tab 时的处理
*/
/**
内层周期 Tab 切换
*/
const
handleTabChange
=
(
name
:
any
)
=>
{
currentPage
.
value
=
1
// 切换周期时页码重置
currentPage
.
value
=
1
loadAllData
()
}
/** 外层公租房/长租房 Tab 切换 */
const
handleRentalTabChange
=
()
=>
{
currentPage
.
value
=
1
// 重置分页
loadAllData
()
}
/** 分页切换 */
const
handlePageChange
=
(
page
:
number
)
=>
{
currentPage
.
value
=
page
fetchWordList
()
// 注意:这里不需要重新请求接口,因为全量数据已存在,仅重新切片展示
}
onMounted
(()
=>
{
...
...
@@ -243,12 +284,12 @@ onMounted(() => {
font-weight
:
bold
;
padding
:
2px
8px
;
border-radius
:
4px
;
&.up
{
color
:
#f56c6c
;
&.up
{
color
:
#f56c6c
;
background-color
:
#fef0f0
;
}
&
.down
{
color
:
#67c23a
;
&
.down
{
color
:
#67c23a
;
background-color
:
#f0f9eb
;
}
}
...
...
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